agora inbox for [email protected]
help / color / mirror / Atom feedAnother modest proposal for docs formatting: catalog descriptions
290+ messages / 6 participants
[nested] [flat]
* Another modest proposal for docs formatting: catalog descriptions
@ 2020-05-05 01:52 Tom Lane <[email protected]>
2020-05-05 02:12 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-05 05:52 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-06-01 22:40 ` Re: Another modest proposal for docs formatting: catalog descriptions Josef Šimánek <[email protected]>
0 siblings, 3 replies; 290+ messages in thread
From: Tom Lane @ 2020-05-05 01:52 UTC (permalink / raw)
To: [email protected]; +Cc: [email protected]
As of HEAD, building the PDF docs for A4 paper draws 538 "contents
... exceed the available area" warnings. While this is a nice step
forward from where we were (v12 has more than 1500 such warnings),
we're far from done fixing that issue.
A large chunk of the remaining warnings are about tables that describe
the columns of system catalogs, system views, and information_schema
views. The typical contents of a row in such a table are a field name,
a field data type, possibly a "references" link, and then a description.
Unsurprisingly, this does not work very well for descriptions of more
than a few words. And not infrequently, we *need* more than a few words.
ISTM this is more or less the same problem we have/had with function
descriptions, and so I'm tempted to solve it in more or less the same
way. Let's redefine the table layout to look like, say, this for
pg_attrdef [1]:
oid oid
Row identifier
adrelid oid (references pg_class.oid)
The table this column belongs to
adnum int2 (references pg_attribute.attnum)
The number of the column
adbin pg_node_tree
The column default value, in nodeToString() representation. Use
pg_get_expr(adbin, adrelid) to convert it to an SQL expression.
That is, let's go over to something that's more or less like a table-ized
<variablelist>, with the fixed items for an entry all written on the first
line, and then as much description text as we need. The actual markup
would be closely modeled on what we did for function-table entries.
Thoughts?
regards, tom lane
[1] https://www.postgresql.org/docs/devel/catalog-pg-attrdef.html
^ permalink raw reply [nested|flat] 290+ messages in thread
* Re: Another modest proposal for docs formatting: catalog descriptions
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
@ 2020-05-05 02:12 ` Jonathan S. Katz <[email protected]>
2 siblings, 0 replies; 290+ messages in thread
From: Jonathan S. Katz @ 2020-05-05 02:12 UTC (permalink / raw)
To: Tom Lane <[email protected]>; [email protected]; +Cc: [email protected]
On 5/4/20 9:52 PM, Tom Lane wrote:
> As of HEAD, building the PDF docs for A4 paper draws 538 "contents
> ... exceed the available area" warnings. While this is a nice step
> forward from where we were (v12 has more than 1500 such warnings),
> we're far from done fixing that issue.
>
> A large chunk of the remaining warnings are about tables that describe
> the columns of system catalogs, system views, and information_schema
> views. The typical contents of a row in such a table are a field name,
> a field data type, possibly a "references" link, and then a description.
> Unsurprisingly, this does not work very well for descriptions of more
> than a few words. And not infrequently, we *need* more than a few words.
>
> ISTM this is more or less the same problem we have/had with function
> descriptions, and so I'm tempted to solve it in more or less the same
> way. Let's redefine the table layout to look like, say, this for
> pg_attrdef [1]:
>
> oid oid
> Row identifier
>
> adrelid oid (references pg_class.oid)
> The table this column belongs to
>
> adnum int2 (references pg_attribute.attnum)
> The number of the column
>
> adbin pg_node_tree
> The column default value, in nodeToString() representation. Use
> pg_get_expr(adbin, adrelid) to convert it to an SQL expression.
>
> That is, let's go over to something that's more or less like a table-ized
> <variablelist>, with the fixed items for an entry all written on the first
> line, and then as much description text as we need. The actual markup
> would be closely modeled on what we did for function-table entries.
>
> Thoughts?
+1. Looks easy enough to read in a plaintext email, and if there are any
minor style nuances on the HTML front, I'm confident we'll solve them.
Jonathan
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 290+ messages in thread
* Re: Another modest proposal for docs formatting: catalog descriptions
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
@ 2020-05-05 05:52 ` Fabien COELHO <[email protected]>
2020-05-05 14:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2 siblings, 1 reply; 290+ messages in thread
From: Fabien COELHO @ 2020-05-05 05:52 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: [email protected]; PostgreSQL Developers <[email protected]>
Hello Tom,
> oid oid
> Row identifier
>
> adrelid oid (references pg_class.oid)
> The table this column belongs to
>
> adnum int2 (references pg_attribute.attnum)
> The number of the column
>
> adbin pg_node_tree
> The column default value, in nodeToString() representation. Use
> pg_get_expr(adbin, adrelid) to convert it to an SQL expression.
>
> Thoughts?
+1
My 0.02€: I'm wondering whether the description could/should match SQL
syntax, eg:
oid OID
adrelid OID REFERENCES pg_class(oid)
adnum INT2 REFERENCES pg_attribute(attnum)
…
Or maybe just uppercase type names, especially when repeated?
oid OID
adrelid OID (references pg_class.oid)
adnum INT2 (references pg_attribute.attnum)
…
I guess that reference targets would still be navigable.
--
Fabien.
^ permalink raw reply [nested|flat] 290+ messages in thread
* Re: Another modest proposal for docs formatting: catalog descriptions
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 05:52 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
@ 2020-05-05 14:27 ` Tom Lane <[email protected]>
2020-05-05 23:42 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-06 05:52 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
0 siblings, 2 replies; 290+ messages in thread
From: Tom Lane @ 2020-05-05 14:27 UTC (permalink / raw)
To: Fabien COELHO <[email protected]>; +Cc: [email protected]; PostgreSQL Developers <[email protected]>
Fabien COELHO <[email protected]> writes:
> My 0.02€: I'm wondering whether the description could/should match SQL
> syntax, eg:
> oid OID
> adrelid OID REFERENCES pg_class(oid)
> adnum INT2 REFERENCES pg_attribute(attnum)
> …
> Or maybe just uppercase type names, especially when repeated?
Meh. I'm not a fan of overuse of upper case --- it's well established
that that's harder to read than lower or mixed case. And it's definitely
project policy that type names are generally treated as identifiers not
keywords, even if some of them happen to be keywords under the hood.
The markup I had in mind was <structfield> for the field name
and <type> for the type name, but no decoration beyond that.
As for the references, it seems to me that your notation would lead
people to think that there are actual FK constraints in place, which
of course there are not (especially not on the views). I'm not
hugely against it but I prefer what I suggested.
> I guess that reference targets would still be navigable.
Yeah, they'd still have <link> wrappers --- if I recall what those
look like in the documentation sources, they don't need any change
except for addition of the "(references ...)" text.
regards, tom lane
^ permalink raw reply [nested|flat] 290+ messages in thread
* Re: Another modest proposal for docs formatting: catalog descriptions
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 05:52 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-05 14:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
@ 2020-05-05 23:42 ` Tom Lane <[email protected]>
2020-05-06 00:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
1 sibling, 1 reply; 290+ messages in thread
From: Tom Lane @ 2020-05-05 23:42 UTC (permalink / raw)
To: Fabien COELHO <[email protected]>; +Cc: [email protected]; PostgreSQL Developers <[email protected]>
Here's a really quick-n-dirty prototype patch that just converts the
pg_aggregate table to the proposed style, plus a screenshot for those
who don't feel like actually building the docs with the patch.
Looking at the results, it seems like we could really use a bit more
horizontal space between the column names and data types, and perhaps
also between the types and the (references) annotations. Other than
that it looks decent. I don't know what's the cleanest way to add
some space there --- I'd rather not have the SGML text do it explicitly.
There's room for complaint that this takes up more vertical space than
the old way, assuming you have a reasonably wide window. I'm not
terribly bothered by that, but maybe someone else will be? I'm inclined
to think that that's well worth the benefit that we won't feel compelled
to keep column descriptions short.
BTW, this being just a test hack, I repurposed the "func_table_entry" and
"func_signature" style marker roles. If we do this for real then of
course we'd want to use different roles, even if they happen to mark up
the same for now.
regards, tom lane
Attachments:
[text/x-diff] pg_aggregate-description.patch (15.1K, ../../[email protected]/2-pg_aggregate-description.patch)
download | inline diff:
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ce33df9..efb5e64 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -388,178 +388,251 @@
<table>
<title><structname>pg_aggregate</structname> Columns</title>
-
- <tgroup cols="4">
+ <tgroup cols="1">
<thead>
<row>
- <entry>Name</entry>
- <entry>Type</entry>
- <entry>References</entry>
- <entry>Description</entry>
+ <entry role="func_table_entry"><para role="func_signature">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
</row>
</thead>
+
<tbody>
<row>
- <entry><structfield>aggfnoid</structfield></entry>
- <entry><type>regproc</type></entry>
- <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
- <entry><structname>pg_proc</structname> OID of the aggregate function</entry>
+ <entry role="func_table_entry"><para role="func_signature">
+ <structfield>aggfnoid</structfield>
+ <type>regproc</type>
+ (references <link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ <structname>pg_proc</structname> OID of the aggregate function
+ </para></entry>
</row>
<row>
- <entry><structfield>aggkind</structfield></entry>
- <entry><type>char</type></entry>
- <entry></entry>
- <entry>Aggregate kind:
+ <entry role="func_table_entry"><para role="func_signature">
+ <structfield>aggkind</structfield>
+ <type>char</type>
+ </para>
+ <para>
+ Aggregate kind:
<literal>n</literal> for <quote>normal</quote> aggregates,
<literal>o</literal> for <quote>ordered-set</quote> aggregates, or
<literal>h</literal> for <quote>hypothetical-set</quote> aggregates
- </entry>
+ </para></entry>
</row>
<row>
- <entry><structfield>aggnumdirectargs</structfield></entry>
- <entry><type>int2</type></entry>
- <entry></entry>
- <entry>Number of direct (non-aggregated) arguments of an ordered-set or
+ <entry role="func_table_entry"><para role="func_signature">
+ <structfield>aggnumdirectargs</structfield>
+ <type>int2</type>
+ </para>
+ <para>
+ Number of direct (non-aggregated) arguments of an ordered-set or
hypothetical-set aggregate, counting a variadic array as one argument.
If equal to <structfield>pronargs</structfield>, the aggregate must be variadic
and the variadic array describes the aggregated arguments as well as
the final direct arguments.
- Always zero for normal aggregates.</entry>
+ Always zero for normal aggregates.
+ </para></entry>
</row>
<row>
- <entry><structfield>aggtransfn</structfield></entry>
- <entry><type>regproc</type></entry>
- <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
- <entry>Transition function</entry>
+ <entry role="func_table_entry"><para role="func_signature">
+ <structfield>aggtransfn</structfield>
+ <type>regproc</type>
+ (references <link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Transition function
+ </para></entry>
</row>
<row>
- <entry><structfield>aggfinalfn</structfield></entry>
- <entry><type>regproc</type></entry>
- <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
- <entry>Final function (zero if none)</entry>
+ <entry role="func_table_entry"><para role="func_signature">
+ <structfield>aggfinalfn</structfield>
+ <type>regproc</type>
+ (references <link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Final function (zero if none)
+ </para></entry>
</row>
<row>
- <entry><structfield>aggcombinefn</structfield></entry>
- <entry><type>regproc</type></entry>
- <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
- <entry>Combine function (zero if none)</entry>
+ <entry role="func_table_entry"><para role="func_signature">
+ <structfield>aggcombinefn</structfield>
+ <type>regproc</type>
+ (references <link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Combine function (zero if none)
+ </para></entry>
</row>
<row>
- <entry><structfield>aggserialfn</structfield></entry>
- <entry><type>regproc</type></entry>
- <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
- <entry>Serialization function (zero if none)</entry>
+ <entry role="func_table_entry"><para role="func_signature">
+ <structfield>aggserialfn</structfield>
+ <type>regproc</type>
+ (references <link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Serialization function (zero if none)
+ </para></entry>
</row>
<row>
- <entry><structfield>aggdeserialfn</structfield></entry>
- <entry><type>regproc</type></entry>
- <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
- <entry>Deserialization function (zero if none)</entry>
+ <entry role="func_table_entry"><para role="func_signature">
+ <structfield>aggdeserialfn</structfield>
+ <type>regproc</type>
+ (references <link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Deserialization function (zero if none)
+ </para></entry>
</row>
<row>
- <entry><structfield>aggmtransfn</structfield></entry>
- <entry><type>regproc</type></entry>
- <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
- <entry>Forward transition function for moving-aggregate mode (zero if none)</entry>
+ <entry role="func_table_entry"><para role="func_signature">
+ <structfield>aggmtransfn</structfield>
+ <type>regproc</type>
+ (references <link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Forward transition function for moving-aggregate mode (zero if none)
+ </para></entry>
</row>
<row>
- <entry><structfield>aggminvtransfn</structfield></entry>
- <entry><type>regproc</type></entry>
- <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
- <entry>Inverse transition function for moving-aggregate mode (zero if none)</entry>
+ <entry role="func_table_entry"><para role="func_signature">
+ <structfield>aggminvtransfn</structfield>
+ <type>regproc</type>
+ (references <link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Inverse transition function for moving-aggregate mode (zero if none)
+ </para></entry>
</row>
<row>
- <entry><structfield>aggmfinalfn</structfield></entry>
- <entry><type>regproc</type></entry>
- <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
- <entry>Final function for moving-aggregate mode (zero if none)</entry>
+ <entry role="func_table_entry"><para role="func_signature">
+ <structfield>aggmfinalfn</structfield>
+ <type>regproc</type>
+ (references <link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Final function for moving-aggregate mode (zero if none)
+ </para></entry>
</row>
<row>
- <entry><structfield>aggfinalextra</structfield></entry>
- <entry><type>bool</type></entry>
- <entry></entry>
- <entry>True to pass extra dummy arguments to <structfield>aggfinalfn</structfield></entry>
+ <entry role="func_table_entry"><para role="func_signature">
+ <structfield>aggfinalextra</structfield>
+ <type>bool</type>
+ </para>
+ <para>
+ True to pass extra dummy arguments to <structfield>aggfinalfn</structfield>
+ </para></entry>
</row>
<row>
- <entry><structfield>aggmfinalextra</structfield></entry>
- <entry><type>bool</type></entry>
- <entry></entry>
- <entry>True to pass extra dummy arguments to <structfield>aggmfinalfn</structfield></entry>
+ <entry role="func_table_entry"><para role="func_signature">
+ <structfield>aggmfinalextra</structfield>
+ <type>bool</type>
+ </para>
+ <para>
+ True to pass extra dummy arguments to <structfield>aggmfinalfn</structfield>
+ </para></entry>
</row>
<row>
- <entry><structfield>aggfinalmodify</structfield></entry>
- <entry><type>char</type></entry>
- <entry></entry>
- <entry>Whether <structfield>aggfinalfn</structfield> modifies the
+ <entry role="func_table_entry"><para role="func_signature">
+ <structfield>aggfinalmodify</structfield>
+ <type>char</type>
+ </para>
+ <para>
+ Whether <structfield>aggfinalfn</structfield> modifies the
transition state value:
<literal>r</literal> if it is read-only,
<literal>s</literal> if the <structfield>aggtransfn</structfield>
cannot be applied after the <structfield>aggfinalfn</structfield>, or
<literal>w</literal> if it writes on the value
- </entry>
+ </para></entry>
</row>
<row>
- <entry><structfield>aggmfinalmodify</structfield></entry>
- <entry><type>char</type></entry>
- <entry></entry>
- <entry>Like <structfield>aggfinalmodify</structfield>, but for
+ <entry role="func_table_entry"><para role="func_signature">
+ <structfield>aggmfinalmodify</structfield>
+ <type>char</type>
+ </para>
+ <para>
+ Like <structfield>aggfinalmodify</structfield>, but for
the <structfield>aggmfinalfn</structfield>
- </entry>
+ </para></entry>
</row>
<row>
- <entry><structfield>aggsortop</structfield></entry>
- <entry><type>oid</type></entry>
- <entry><literal><link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.oid</literal></entry>
- <entry>Associated sort operator (zero if none)</entry>
+ <entry role="func_table_entry"><para role="func_signature">
+ <structfield>aggsortop</structfield>
+ <type>oid</type>
+ (references <link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Associated sort operator (zero if none)
+ </para></entry>
</row>
<row>
- <entry><structfield>aggtranstype</structfield></entry>
- <entry><type>oid</type></entry>
- <entry><literal><link linkend="catalog-pg-type"><structname>pg_type</structname></link>.oid</literal></entry>
- <entry>Data type of the aggregate function's internal transition (state) data</entry>
+ <entry role="func_table_entry"><para role="func_signature">
+ <structfield>aggtranstype</structfield>
+ <type>oid</type>
+ (references <link linkend="catalog-pg-type"><structname>pg_type</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Data type of the aggregate function's internal transition (state) data
+ </para></entry>
</row>
<row>
- <entry><structfield>aggtransspace</structfield></entry>
- <entry><type>int4</type></entry>
- <entry></entry>
- <entry>Approximate average size (in bytes) of the transition state
- data, or zero to use a default estimate</entry>
+ <entry role="func_table_entry"><para role="func_signature">
+ <structfield>aggtransspace</structfield>
+ <type>int4</type>
+ </para>
+ <para>
+ Approximate average size (in bytes) of the transition state
+ data, or zero to use a default estimate
+ </para></entry>
</row>
<row>
- <entry><structfield>aggmtranstype</structfield></entry>
- <entry><type>oid</type></entry>
- <entry><literal><link linkend="catalog-pg-type"><structname>pg_type</structname></link>.oid</literal></entry>
- <entry>Data type of the aggregate function's internal transition (state)
- data for moving-aggregate mode (zero if none)</entry>
+ <entry role="func_table_entry"><para role="func_signature">
+ <structfield>aggmtranstype</structfield>
+ <type>oid</type>
+ (references <link linkend="catalog-pg-type"><structname>pg_type</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Data type of the aggregate function's internal transition (state)
+ data for moving-aggregate mode (zero if none)
+ </para></entry>
</row>
<row>
- <entry><structfield>aggmtransspace</structfield></entry>
- <entry><type>int4</type></entry>
- <entry></entry>
- <entry>Approximate average size (in bytes) of the transition state data
- for moving-aggregate mode, or zero to use a default estimate</entry>
+ <entry role="func_table_entry"><para role="func_signature">
+ <structfield>aggmtransspace</structfield>
+ <type>int4</type>
+ </para>
+ <para>
+ Approximate average size (in bytes) of the transition state data
+ for moving-aggregate mode, or zero to use a default estimate
+ </para></entry>
</row>
<row>
- <entry><structfield>agginitval</structfield></entry>
- <entry><type>text</type></entry>
- <entry></entry>
- <entry>
+ <entry role="func_table_entry"><para role="func_signature">
+ <structfield>agginitval</structfield>
+ <type>text</type>
+ </para>
+ <para>
The initial value of the transition state. This is a text
field containing the initial value in its external string
representation. If this field is null, the transition state
value starts out null.
- </entry>
+ </para></entry>
</row>
<row>
- <entry><structfield>aggminitval</structfield></entry>
- <entry><type>text</type></entry>
- <entry></entry>
- <entry>
+ <entry role="func_table_entry"><para role="func_signature">
+ <structfield>aggminitval</structfield>
+ <type>text</type>
+ </para>
+ <para>
The initial value of the transition state for moving-aggregate mode.
This is a text field containing the initial value in its external
string representation. If this field is null, the transition state
value starts out null.
- </entry>
+ </para></entry>
</row>
</tbody>
</tgroup>
[image/png] pg_aggregate.png (338.6K, ../../[email protected]/3-pg_aggregate.png)
download | view image
^ permalink raw reply [nested|flat] 290+ messages in thread
* Re: Another modest proposal for docs formatting: catalog descriptions
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 05:52 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-05 14:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 23:42 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
@ 2020-05-06 00:27 ` Jonathan S. Katz <[email protected]>
2020-05-06 21:18 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
0 siblings, 1 reply; 290+ messages in thread
From: Jonathan S. Katz @ 2020-05-06 00:27 UTC (permalink / raw)
To: Tom Lane <[email protected]>; Fabien COELHO <[email protected]>; +Cc: [email protected]; PostgreSQL Developers <[email protected]>
On 5/5/20 7:42 PM, Tom Lane wrote:
> Here's a really quick-n-dirty prototype patch that just converts the
> pg_aggregate table to the proposed style, plus a screenshot for those
> who don't feel like actually building the docs with the patch.
Not opposed to building the docs, but the screenshot expedites things ;)
> Looking at the results, it seems like we could really use a bit more
> horizontal space between the column names and data types, and perhaps
> also between the types and the (references) annotations. Other than
> that it looks decent. I don't know what's the cleanest way to add
> some space there --- I'd rather not have the SGML text do it explicitly.
If each element (i.e. column name, data type) is wrapped in a HTML
element with its own class (e.g. a span) it's fairly easy to add that
space with CSS. I'm not sure the ramifications for the PDFs though.
> There's room for complaint that this takes up more vertical space than
> the old way, assuming you have a reasonably wide window. I'm not
> terribly bothered by that, but maybe someone else will be? I'm inclined
> to think that that's well worth the benefit that we won't feel compelled
> to keep column descriptions short.
I think for reference materials, vertical space is acceptable. It seems
to be the "mobile way" of doing things, since people are scrolling down.
(And unlike the mailing lists, we don't need to keep the font small ;)
Anyway, +1
> BTW, this being just a test hack, I repurposed the "func_table_entry" and
> "func_signature" style marker roles. If we do this for real then of
> course we'd want to use different roles, even if they happen to mark up
> the same for now.
Agreed.
Thanks,
Jonathan
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 290+ messages in thread
* Re: Another modest proposal for docs formatting: catalog descriptions
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 05:52 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-05 14:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 23:42 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-06 00:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
@ 2020-05-06 21:18 ` Alvaro Herrera <[email protected]>
2020-05-06 21:24 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
0 siblings, 1 reply; 290+ messages in thread
From: Alvaro Herrera @ 2020-05-06 21:18 UTC (permalink / raw)
To: Jonathan S. Katz <[email protected]>; +Cc: Tom Lane <[email protected]>; Fabien COELHO <[email protected]>; [email protected]; PostgreSQL Developers <[email protected]>
Hello
I think the recent changes to CSS might have broken things in the XSLT
build; apparently the SGML tooling did things differently. Compare the
screenshot of tables 67.2 and 67.3 ... 9.6 on the left, master on the
right. Is the latter formatting correct/desirable?
Thanks
--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
[image/png] brin-css.png (314.8K, ../../[email protected]/2-brin-css.png)
download | view image
^ permalink raw reply [nested|flat] 290+ messages in thread
* Re: Another modest proposal for docs formatting: catalog descriptions
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 05:52 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-05 14:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 23:42 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-06 00:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:18 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
@ 2020-05-06 21:24 ` Jonathan S. Katz <[email protected]>
2020-05-06 21:38 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
0 siblings, 1 reply; 290+ messages in thread
From: Jonathan S. Katz @ 2020-05-06 21:24 UTC (permalink / raw)
To: Alvaro Herrera <[email protected]>; +Cc: Tom Lane <[email protected]>; Fabien COELHO <[email protected]>; [email protected]; PostgreSQL Developers <[email protected]>
On 5/6/20 5:18 PM, Alvaro Herrera wrote:
> Hello
>
> I think the recent changes to CSS might have broken things in the XSLT
> build; apparently the SGML tooling did things differently. Compare the
> screenshot of tables 67.2 and 67.3 ... 9.6 on the left, master on the
> right. Is the latter formatting correct/desirable?
I know that 9.6 uses a different subset of the styles, and I recall the
text being blue during the original conversion. For example, the "table"
in the 9.6 docs has a class of "CALSTABLE" whereas in master, it is
"table" (and we operate off of it as "table.table" when doing lookups,
to ensure anything else with class "table" is unaffected).
There's also not as much control over some of the older documentation as
there are less classes we can bind the CSS too.
The latest changes should only affect master (devel) and beyond.
Jonathan
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 290+ messages in thread
* Re: Another modest proposal for docs formatting: catalog descriptions
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 05:52 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-05 14:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 23:42 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-06 00:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:18 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
2020-05-06 21:24 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
@ 2020-05-06 21:38 ` Alvaro Herrera <[email protected]>
2020-05-06 22:56 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-18 20:11 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
0 siblings, 2 replies; 290+ messages in thread
From: Alvaro Herrera @ 2020-05-06 21:38 UTC (permalink / raw)
To: Jonathan S. Katz <[email protected]>; +Cc: Tom Lane <[email protected]>; Fabien COELHO <[email protected]>; [email protected]; PostgreSQL Developers <[email protected]>
On 2020-May-06, Jonathan S. Katz wrote:
> I know that 9.6 uses a different subset of the styles, and I recall the
> text being blue during the original conversion. For example, the "table"
> in the 9.6 docs has a class of "CALSTABLE" whereas in master, it is
> "table" (and we operate off of it as "table.table" when doing lookups,
> to ensure anything else with class "table" is unaffected).
>
> There's also not as much control over some of the older documentation as
> there are less classes we can bind the CSS too.
>
> The latest changes should only affect master (devel) and beyond.
... oh, okay. I guess I was reporting that the font on the new version
seems to have got smaller. Looking at other pages, it appears that the
font is indeed a lot smaller in all tables, including those Tom has been
editing. So maybe this is desirable for some reason. I'll have to keep
my magnifying glass handy, I suppose.
Anyway, it seems <computeroutput> or whatever tag has been used in some
of these new tables makes the font be larger. Another screenshot is
attached to show this. Is this likewise desired? It also shows that
the main text body is sized similar to the <computeroutput> tagged text,
not the table contents text. (The browser is Brave, a Chromium
derivative.)
--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Attachments:
[image/png] json-css.png (37.6K, ../../[email protected]/2-json-css.png)
download | view image
^ permalink raw reply [nested|flat] 290+ messages in thread
* Re: Another modest proposal for docs formatting: catalog descriptions
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 05:52 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-05 14:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 23:42 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-06 00:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:18 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
2020-05-06 21:24 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:38 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
@ 2020-05-06 22:56 ` Tom Lane <[email protected]>
2020-05-08 22:53 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
1 sibling, 1 reply; 290+ messages in thread
From: Tom Lane @ 2020-05-06 22:56 UTC (permalink / raw)
To: Alvaro Herrera <[email protected]>; +Cc: Jonathan S. Katz <[email protected]>; Fabien COELHO <[email protected]>; [email protected]; PostgreSQL Developers <[email protected]>
Alvaro Herrera <[email protected]> writes:
> ... oh, okay. I guess I was reporting that the font on the new version
> seems to have got smaller. Looking at other pages, it appears that the
> font is indeed a lot smaller in all tables, including those Tom has been
> editing. So maybe this is desirable for some reason. I'll have to keep
> my magnifying glass handy, I suppose.
Huh, browser specific maybe? The font doesn't seem any smaller to me,
using Safari.
> Anyway, it seems <computeroutput> or whatever tag has been used in some
> of these new tables makes the font be larger. Another screenshot is
> attached to show this. Is this likewise desired? It also shows that
> the main text body is sized similar to the <computeroutput> tagged text,
> not the table contents text. (The browser is Brave, a Chromium
> derivative.)
I'm not getting that, either; to me it looks as attached. I agree
what you're seeing is not as-intended.
regards, tom lane
Attachments:
[image/png] safari-json-table.png (113.5K, ../../[email protected]/2-safari-json-table.png)
download | view image
^ permalink raw reply [nested|flat] 290+ messages in thread
* Re: Another modest proposal for docs formatting: catalog descriptions
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 05:52 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-05 14:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 23:42 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-06 00:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:18 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
2020-05-06 21:24 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:38 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
2020-05-06 22:56 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
@ 2020-05-08 22:53 ` Tom Lane <[email protected]>
2020-05-09 06:54 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
0 siblings, 1 reply; 290+ messages in thread
From: Tom Lane @ 2020-05-08 22:53 UTC (permalink / raw)
To: Jonathan S. Katz <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Fabien COELHO <[email protected]>; [email protected]; PostgreSQL Developers <[email protected]>
Here's a more fully fleshed out draft for this, with stylesheet
markup to get extra space around the column type names.
I realized that I can probably automate this conversion, unlike the
function-table conversion: I'm not feeling any need to editorialize
on the column descriptions, so I can probably just extract the data
from the SGML programmatically and rebuild it as I want. Seems like
it should be a fairly quick process. So, if this seems good from
your standpoint, please push up the patch on main.css and then I'll
see about doing the edit.
(BTW, said patch also removes some no-longer-used detritus from the
earlier markup attempt.)
regards, tom lane
Attachments:
[text/x-diff] catalog-description-prototype-2.patch (17.0K, ../../[email protected]/2-catalog-description-prototype-2.patch)
download | inline diff:
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index ce33df9..e3513ad 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -388,178 +388,251 @@
<table>
<title><structname>pg_aggregate</structname> Columns</title>
-
- <tgroup cols="4">
+ <tgroup cols="1">
<thead>
<row>
- <entry>Name</entry>
- <entry>Type</entry>
- <entry>References</entry>
- <entry>Description</entry>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ Column Type
+ </para>
+ <para>
+ Description
+ </para></entry>
</row>
</thead>
+
<tbody>
<row>
- <entry><structfield>aggfnoid</structfield></entry>
- <entry><type>regproc</type></entry>
- <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
- <entry><structname>pg_proc</structname> OID of the aggregate function</entry>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>aggfnoid</structfield>
+ <type>regproc</type>
+ (references <link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ <structname>pg_proc</structname> OID of the aggregate function
+ </para></entry>
</row>
<row>
- <entry><structfield>aggkind</structfield></entry>
- <entry><type>char</type></entry>
- <entry></entry>
- <entry>Aggregate kind:
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>aggkind</structfield>
+ <type>char</type>
+ </para>
+ <para>
+ Aggregate kind:
<literal>n</literal> for <quote>normal</quote> aggregates,
<literal>o</literal> for <quote>ordered-set</quote> aggregates, or
<literal>h</literal> for <quote>hypothetical-set</quote> aggregates
- </entry>
+ </para></entry>
</row>
<row>
- <entry><structfield>aggnumdirectargs</structfield></entry>
- <entry><type>int2</type></entry>
- <entry></entry>
- <entry>Number of direct (non-aggregated) arguments of an ordered-set or
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>aggnumdirectargs</structfield>
+ <type>int2</type>
+ </para>
+ <para>
+ Number of direct (non-aggregated) arguments of an ordered-set or
hypothetical-set aggregate, counting a variadic array as one argument.
If equal to <structfield>pronargs</structfield>, the aggregate must be variadic
and the variadic array describes the aggregated arguments as well as
the final direct arguments.
- Always zero for normal aggregates.</entry>
+ Always zero for normal aggregates.
+ </para></entry>
</row>
<row>
- <entry><structfield>aggtransfn</structfield></entry>
- <entry><type>regproc</type></entry>
- <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
- <entry>Transition function</entry>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>aggtransfn</structfield>
+ <type>regproc</type>
+ (references <link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Transition function
+ </para></entry>
</row>
<row>
- <entry><structfield>aggfinalfn</structfield></entry>
- <entry><type>regproc</type></entry>
- <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
- <entry>Final function (zero if none)</entry>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>aggfinalfn</structfield>
+ <type>regproc</type>
+ (references <link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Final function (zero if none)
+ </para></entry>
</row>
<row>
- <entry><structfield>aggcombinefn</structfield></entry>
- <entry><type>regproc</type></entry>
- <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
- <entry>Combine function (zero if none)</entry>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>aggcombinefn</structfield>
+ <type>regproc</type>
+ (references <link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Combine function (zero if none)
+ </para></entry>
</row>
<row>
- <entry><structfield>aggserialfn</structfield></entry>
- <entry><type>regproc</type></entry>
- <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
- <entry>Serialization function (zero if none)</entry>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>aggserialfn</structfield>
+ <type>regproc</type>
+ (references <link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Serialization function (zero if none)
+ </para></entry>
</row>
<row>
- <entry><structfield>aggdeserialfn</structfield></entry>
- <entry><type>regproc</type></entry>
- <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
- <entry>Deserialization function (zero if none)</entry>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>aggdeserialfn</structfield>
+ <type>regproc</type>
+ (references <link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Deserialization function (zero if none)
+ </para></entry>
</row>
<row>
- <entry><structfield>aggmtransfn</structfield></entry>
- <entry><type>regproc</type></entry>
- <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
- <entry>Forward transition function for moving-aggregate mode (zero if none)</entry>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>aggmtransfn</structfield>
+ <type>regproc</type>
+ (references <link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Forward transition function for moving-aggregate mode (zero if none)
+ </para></entry>
</row>
<row>
- <entry><structfield>aggminvtransfn</structfield></entry>
- <entry><type>regproc</type></entry>
- <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
- <entry>Inverse transition function for moving-aggregate mode (zero if none)</entry>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>aggminvtransfn</structfield>
+ <type>regproc</type>
+ (references <link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Inverse transition function for moving-aggregate mode (zero if none)
+ </para></entry>
</row>
<row>
- <entry><structfield>aggmfinalfn</structfield></entry>
- <entry><type>regproc</type></entry>
- <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
- <entry>Final function for moving-aggregate mode (zero if none)</entry>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>aggmfinalfn</structfield>
+ <type>regproc</type>
+ (references <link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Final function for moving-aggregate mode (zero if none)
+ </para></entry>
</row>
<row>
- <entry><structfield>aggfinalextra</structfield></entry>
- <entry><type>bool</type></entry>
- <entry></entry>
- <entry>True to pass extra dummy arguments to <structfield>aggfinalfn</structfield></entry>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>aggfinalextra</structfield>
+ <type>bool</type>
+ </para>
+ <para>
+ True to pass extra dummy arguments to <structfield>aggfinalfn</structfield>
+ </para></entry>
</row>
<row>
- <entry><structfield>aggmfinalextra</structfield></entry>
- <entry><type>bool</type></entry>
- <entry></entry>
- <entry>True to pass extra dummy arguments to <structfield>aggmfinalfn</structfield></entry>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>aggmfinalextra</structfield>
+ <type>bool</type>
+ </para>
+ <para>
+ True to pass extra dummy arguments to <structfield>aggmfinalfn</structfield>
+ </para></entry>
</row>
<row>
- <entry><structfield>aggfinalmodify</structfield></entry>
- <entry><type>char</type></entry>
- <entry></entry>
- <entry>Whether <structfield>aggfinalfn</structfield> modifies the
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>aggfinalmodify</structfield>
+ <type>char</type>
+ </para>
+ <para>
+ Whether <structfield>aggfinalfn</structfield> modifies the
transition state value:
<literal>r</literal> if it is read-only,
<literal>s</literal> if the <structfield>aggtransfn</structfield>
cannot be applied after the <structfield>aggfinalfn</structfield>, or
<literal>w</literal> if it writes on the value
- </entry>
+ </para></entry>
</row>
<row>
- <entry><structfield>aggmfinalmodify</structfield></entry>
- <entry><type>char</type></entry>
- <entry></entry>
- <entry>Like <structfield>aggfinalmodify</structfield>, but for
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>aggmfinalmodify</structfield>
+ <type>char</type>
+ </para>
+ <para>
+ Like <structfield>aggfinalmodify</structfield>, but for
the <structfield>aggmfinalfn</structfield>
- </entry>
+ </para></entry>
</row>
<row>
- <entry><structfield>aggsortop</structfield></entry>
- <entry><type>oid</type></entry>
- <entry><literal><link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.oid</literal></entry>
- <entry>Associated sort operator (zero if none)</entry>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>aggsortop</structfield>
+ <type>oid</type>
+ (references <link linkend="catalog-pg-operator"><structname>pg_operator</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Associated sort operator (zero if none)
+ </para></entry>
</row>
<row>
- <entry><structfield>aggtranstype</structfield></entry>
- <entry><type>oid</type></entry>
- <entry><literal><link linkend="catalog-pg-type"><structname>pg_type</structname></link>.oid</literal></entry>
- <entry>Data type of the aggregate function's internal transition (state) data</entry>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>aggtranstype</structfield>
+ <type>oid</type>
+ (references <link linkend="catalog-pg-type"><structname>pg_type</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Data type of the aggregate function's internal transition (state) data
+ </para></entry>
</row>
<row>
- <entry><structfield>aggtransspace</structfield></entry>
- <entry><type>int4</type></entry>
- <entry></entry>
- <entry>Approximate average size (in bytes) of the transition state
- data, or zero to use a default estimate</entry>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>aggtransspace</structfield>
+ <type>int4</type>
+ </para>
+ <para>
+ Approximate average size (in bytes) of the transition state
+ data, or zero to use a default estimate
+ </para></entry>
</row>
<row>
- <entry><structfield>aggmtranstype</structfield></entry>
- <entry><type>oid</type></entry>
- <entry><literal><link linkend="catalog-pg-type"><structname>pg_type</structname></link>.oid</literal></entry>
- <entry>Data type of the aggregate function's internal transition (state)
- data for moving-aggregate mode (zero if none)</entry>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>aggmtranstype</structfield>
+ <type>oid</type>
+ (references <link linkend="catalog-pg-type"><structname>pg_type</structname></link>.<structfield>oid</structfield>)
+ </para>
+ <para>
+ Data type of the aggregate function's internal transition (state)
+ data for moving-aggregate mode (zero if none)
+ </para></entry>
</row>
<row>
- <entry><structfield>aggmtransspace</structfield></entry>
- <entry><type>int4</type></entry>
- <entry></entry>
- <entry>Approximate average size (in bytes) of the transition state data
- for moving-aggregate mode, or zero to use a default estimate</entry>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>aggmtransspace</structfield>
+ <type>int4</type>
+ </para>
+ <para>
+ Approximate average size (in bytes) of the transition state data
+ for moving-aggregate mode, or zero to use a default estimate
+ </para></entry>
</row>
<row>
- <entry><structfield>agginitval</structfield></entry>
- <entry><type>text</type></entry>
- <entry></entry>
- <entry>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>agginitval</structfield>
+ <type>text</type>
+ </para>
+ <para>
The initial value of the transition state. This is a text
field containing the initial value in its external string
representation. If this field is null, the transition state
value starts out null.
- </entry>
+ </para></entry>
</row>
<row>
- <entry><structfield>aggminitval</structfield></entry>
- <entry><type>text</type></entry>
- <entry></entry>
- <entry>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>aggminitval</structfield>
+ <type>text</type>
+ </para>
+ <para>
The initial value of the transition state for moving-aggregate mode.
This is a text field containing the initial value in its external
string representation. If this field is null, the transition state
value starts out null.
- </entry>
+ </para></entry>
</row>
</tbody>
</tgroup>
diff --git a/doc/src/sgml/stylesheet-fo.xsl b/doc/src/sgml/stylesheet-fo.xsl
index 5a27346..861e7ed 100644
--- a/doc/src/sgml/stylesheet-fo.xsl
+++ b/doc/src/sgml/stylesheet-fo.xsl
@@ -78,6 +78,16 @@
</fo:block>
</xsl:template>
+<!-- formatting for entries in tables of catalog/view columns -->
+<xsl:template match="entry[@role='catalog_table_entry']/para">
+ <fo:block margin-left="4em" text-align="left">
+ <xsl:if test="self::para[@role='column_definition']">
+ <xsl:attribute name="text-indent">-3.5em</xsl:attribute>
+ </xsl:if>
+ <xsl:apply-templates/>
+ </fo:block>
+</xsl:template>
+
<!-- overrides stylesheet-common.xsl -->
<!-- FOP needs us to be explicit about the font to use for right arrow -->
<xsl:template match="returnvalue">
diff --git a/doc/src/sgml/stylesheet.css b/doc/src/sgml/stylesheet.css
index 11f88cc..b9317c5 100644
--- a/doc/src/sgml/stylesheet.css
+++ b/doc/src/sgml/stylesheet.css
@@ -96,6 +96,36 @@ td.func_table_entry pre.programlisting {
padding-left: 4em;
}
+/* formatting for entries in tables of catalog/view columns */
+
+th.catalog_table_entry p,
+td.catalog_table_entry p {
+ margin-top: 0.1em;
+ margin-bottom: 0.1em;
+ padding-left: 4em;
+ text-align: left;
+}
+
+th.catalog_table_entry p.column_definition {
+ text-indent: -3.5em;
+ word-spacing: 1em;
+}
+
+td.catalog_table_entry p.column_definition {
+ text-indent: -3.5em;
+}
+
+p.column_definition code.type {
+ padding-left: 1em;
+ padding-right: 1em;
+}
+
+td.catalog_table_entry pre.programlisting {
+ margin-top: 0.1em;
+ margin-bottom: 0.1em;
+ padding-left: 4em;
+}
+
/* Put these here instead of inside the HTML (see unsetting of
admon.style in XSL) so that the web site stylesheet can set its own
style. */
[text/x-diff] website-style.patch (1.3K, ../../[email protected]/3-website-style.patch)
download | inline diff:
--- main.css.orig 2020-05-08 18:19:11.711175113 -0400
+++ main.css 2020-05-08 18:32:04.235265137 -0400
@@ -792,13 +792,6 @@
}
/** Formatting for entries in tables of functions: indent all but first line **/
-#docContent table.table th.functableentry,
-#docContent table.table td.functableentry {
- padding-left: 4em;
- text-indent: -3.5em;
- text-align: left;
-}
-
#docContent table.table th.func_table_entry p,
#docContent table.table td.func_table_entry p {
margin-top: 0.1em;
@@ -820,6 +813,38 @@
padding-left: 4em;
}
+/** Formatting for entries in tables of catalog/view columns **/
+#docContent table.table th.catalog_table_entry p,
+#docContent table.table td.catalog_table_entry p {
+ margin-top: 0.1em;
+ margin-bottom: 0.1em;
+ padding-left: 4em;
+ text-align: left;
+}
+
+#docContent table.table th.catalog_table_entry p.column_definition {
+ text-indent: -3.5em;
+ word-spacing: 1em;
+}
+
+#docContent table.table td.catalog_table_entry p.column_definition {
+ text-indent: -3.5em;
+}
+
+#docContent table.table p.column_definition code.type {
+ padding-left: 1em;
+ padding-right: 1em;
+}
+
+#docContent table.table td.catalog_table_entry pre.programlisting {
+ background-color: inherit;
+ border: 0;
+ margin-bottom: 0.1em;
+ margin-top: 0.1em;
+ padding: 0;
+ padding-left: 4em;
+}
+
/**
* Titles, Navigation
*/
[image/png] pg-aggregate-v2.png (193.9K, ../../[email protected]/4-pg-aggregate-v2.png)
download | view image
^ permalink raw reply [nested|flat] 290+ messages in thread
* Re: Another modest proposal for docs formatting: catalog descriptions
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 05:52 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-05 14:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 23:42 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-06 00:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:18 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
2020-05-06 21:24 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:38 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
2020-05-06 22:56 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-08 22:53 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
@ 2020-05-09 06:54 ` Fabien COELHO <[email protected]>
2020-05-09 15:34 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
0 siblings, 1 reply; 290+ messages in thread
From: Fabien COELHO @ 2020-05-09 06:54 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Jonathan S. Katz <[email protected]>; Alvaro Herrera <[email protected]>; [email protected]; PostgreSQL Developers <[email protected]>
Hello Tom,
> Here's a more fully fleshed out draft for this, with stylesheet
> markup to get extra space around the column type names.
I find this added spacing awkward, espacially as attribute names are
always one word anyway. I prefer the non spaced approach.
If spacing is discussed, should the layout rather try to align type
information, eg:
attribute type
description
---
foo bla
this does this and that ...
and here is an example about it
---
foo-foo-foo bla-bla
whatever bla bla blah bla foo foo foo ...
and stuff
I'm not sure how achievable it is from a xml & processing point of view,
and how desirable it is, I'm just throwing it for consideration.
--
Fabien.
^ permalink raw reply [nested|flat] 290+ messages in thread
* Re: Another modest proposal for docs formatting: catalog descriptions
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 05:52 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-05 14:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 23:42 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-06 00:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:18 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
2020-05-06 21:24 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:38 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
2020-05-06 22:56 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-08 22:53 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-09 06:54 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
@ 2020-05-09 15:34 ` Tom Lane <[email protected]>
2020-05-09 17:30 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
0 siblings, 1 reply; 290+ messages in thread
From: Tom Lane @ 2020-05-09 15:34 UTC (permalink / raw)
To: Fabien COELHO <[email protected]>; +Cc: Jonathan S. Katz <[email protected]>; Alvaro Herrera <[email protected]>; [email protected]; PostgreSQL Developers <[email protected]>
Fabien COELHO <[email protected]> writes:
>> Here's a more fully fleshed out draft for this, with stylesheet
>> markup to get extra space around the column type names.
> I find this added spacing awkward, espacially as attribute names are
> always one word anyway. I prefer the non spaced approach.
It's certainly arguable that that look is too heavy-handed. In the
attached, I knocked down the extra space from 1em to 0.25em, which
makes it quite a bit subtler --- are you any happier with this?
BTW, I don't think it's very accurate that "attribute names are
always one word" --- see the second attachment. Here if anything
I'm wanting a little more space.
> If spacing is discussed, should the layout rather try to align type
> information, eg:
I thought about that, but it seems extremely close to some of the
earlier function-table layouts that were so widely panned. The SGML
source would have to be a lot uglier too, probably with explicit use
of spanspec's on every row. It could be done no doubt, but I think
people would not see it as an improvement.
regards, tom lane
Attachments:
[image/png] pg-aggregate-quarter-em.png (196.7K, ../../[email protected]/2-pg-aggregate-quarter-em.png)
download | view image
[image/png] info-schema-quarter-em.png (141.5K, ../../[email protected]/3-info-schema-quarter-em.png)
download | view image
^ permalink raw reply [nested|flat] 290+ messages in thread
* Re: Another modest proposal for docs formatting: catalog descriptions
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 05:52 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-05 14:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 23:42 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-06 00:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:18 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
2020-05-06 21:24 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:38 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
2020-05-06 22:56 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-08 22:53 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-09 06:54 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-09 15:34 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
@ 2020-05-09 17:30 ` Fabien COELHO <[email protected]>
2020-05-09 18:09 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
0 siblings, 1 reply; 290+ messages in thread
From: Fabien COELHO @ 2020-05-09 17:30 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Jonathan S. Katz <[email protected]>; Alvaro Herrera <[email protected]>; [email protected]; PostgreSQL Developers <[email protected]>
Hello Tom,
>>> Here's a more fully fleshed out draft for this, with stylesheet
>>> markup to get extra space around the column type names.
>
>> I find this added spacing awkward, espacially as attribute names are
>> always one word anyway. I prefer the non spaced approach.
>
> It's certainly arguable that that look is too heavy-handed. In the
> attached, I knocked down the extra space from 1em to 0.25em, which
> makes it quite a bit subtler --- are you any happier with this?
Yes, definitely.
> BTW, I don't think it's very accurate that "attribute names are
> always one word" --- see the second attachment.
Indeed.
> Here if anything I'm wanting a little more space.
I'm fine with 0.25em which allows some breathing without looking awkward.
Maybe a little more would still be okay, but not much more.
>> If spacing is discussed, should the layout rather try to align type
>> information, eg:
>
> I thought about that, but it seems extremely close to some of the
> earlier function-table layouts that were so widely panned. The SGML
> source would have to be a lot uglier too, probably with explicit use
> of spanspec's on every row.
Hmmm, that's the kind of things I was afraid of.
> It could be done no doubt, but I think people would not see it as an
> improvement.
Possibly. I'm a little at odds with Type not being above types, but far on
the left, so that you cannot really "see" that it is about the format,
especially with long attribute names:
Column Type
Description
quite_a_long_attribute and_its_type
...
The horizontal distance between "Type" and "and_its_type" is so wide as to
hide the clue that the former is describing the later. But maybe aligning
would be too ugly.
If I can suggest more adjustements, maybe the description margin is a too
much, I'd propose reduce it to about 3 chars wide. Obviously any aesthetic
opinion is by definition subjective and prone to differ from one person to
the next…
--
Fabien.
^ permalink raw reply [nested|flat] 290+ messages in thread
* Re: Another modest proposal for docs formatting: catalog descriptions
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 05:52 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-05 14:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 23:42 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-06 00:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:18 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
2020-05-06 21:24 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:38 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
2020-05-06 22:56 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-08 22:53 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-09 06:54 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-09 15:34 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-09 17:30 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
@ 2020-05-09 18:09 ` Tom Lane <[email protected]>
2020-05-10 16:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
0 siblings, 1 reply; 290+ messages in thread
From: Tom Lane @ 2020-05-09 18:09 UTC (permalink / raw)
To: Fabien COELHO <[email protected]>; +Cc: Jonathan S. Katz <[email protected]>; Alvaro Herrera <[email protected]>; [email protected]; PostgreSQL Developers <[email protected]>
Fabien COELHO <[email protected]> writes:
> Possibly. I'm a little at odds with Type not being above types, but far on
> the left, so that you cannot really "see" that it is about the format,
Yeah, agreed. We can adjust the space in the header independently of
what's in the table entries, so it'd be possible to put more space
between "Column" and "Type" ... but I'm not sure if that would fix it.
> If I can suggest more adjustements, maybe the description margin is a too
> much, I'd propose reduce it to about 3 chars wide. Obviously any aesthetic
> opinion is by definition subjective and prone to differ from one person to
> the next…
This is more Jonathan's department than mine, but I personally prefer more
indentation to less --- you want the column names to stick out so you can
find them. Anyway, the present indentation is (it looks like) the same
as we are using in <variablelist>s, which this layout is based on.
regards, tom lane
^ permalink raw reply [nested|flat] 290+ messages in thread
* Re: Another modest proposal for docs formatting: catalog descriptions
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 05:52 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-05 14:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 23:42 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-06 00:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:18 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
2020-05-06 21:24 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:38 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
2020-05-06 22:56 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-08 22:53 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-09 06:54 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-09 15:34 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-09 17:30 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-09 18:09 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
@ 2020-05-10 16:27 ` Tom Lane <[email protected]>
2020-05-10 18:03 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
0 siblings, 1 reply; 290+ messages in thread
From: Tom Lane @ 2020-05-10 16:27 UTC (permalink / raw)
To: Jonathan S. Katz <[email protected]>; +Cc: Fabien COELHO <[email protected]>; Alvaro Herrera <[email protected]>; [email protected]; PostgreSQL Developers <[email protected]>
Just FTR, here's a complete patch for this. I successfully regenerated
the column names, types, and ordering from the system catalogs, and
plugged the descriptions back into that by dint of parsing them out of
the XML. The "references" data was based on findoidjoins' results plus
hand additions to cover all the cases we are calling out now (there are
a lot of "references" links for attnums and a few other non-OID columns,
plus references links for views which findoidjoins doesn't consider).
So I have pretty high confidence that this info is OK. I'd be too
embarrassed to show anybody the code though ;-) ... it was just a brute
force hack.
regards, tom lane
Attachments:
[application/x-gzip] catalog-reformat.patch.gz (78.4K, ../../[email protected]/2-catalog-reformat.patch.gz)
download
[text/x-diff] website.patch (1.3K, ../../[email protected]/3-website.patch)
download | inline diff:
--- main.css~ 2020-05-10 12:18:39.773129302 -0400
+++ main.css 2020-05-09 21:46:22.424776849 -0400
@@ -792,13 +792,6 @@
}
/** Formatting for entries in tables of functions: indent all but first line **/
-#docContent table.table th.functableentry,
-#docContent table.table td.functableentry {
- padding-left: 4em;
- text-indent: -3.5em;
- text-align: left;
-}
-
#docContent table.table th.func_table_entry p,
#docContent table.table td.func_table_entry p {
margin-top: 0.1em;
@@ -820,6 +813,38 @@
padding-left: 4em;
}
+/** Formatting for entries in tables of catalog/view columns **/
+#docContent table.table th.catalog_table_entry p,
+#docContent table.table td.catalog_table_entry p {
+ margin-top: 0.1em;
+ margin-bottom: 0.1em;
+ padding-left: 4em;
+ text-align: left;
+}
+
+#docContent table.table th.catalog_table_entry p.column_definition {
+ text-indent: -3.5em;
+ word-spacing: 0.25em;
+}
+
+#docContent table.table td.catalog_table_entry p.column_definition {
+ text-indent: -3.5em;
+}
+
+#docContent table.table p.column_definition code.type {
+ padding-left: 0.25em;
+ padding-right: 0.25em;
+}
+
+#docContent table.table td.catalog_table_entry pre.programlisting {
+ background-color: inherit;
+ border: 0;
+ margin-bottom: 0.1em;
+ margin-top: 0.1em;
+ padding: 0;
+ padding-left: 4em;
+}
+
/**
* Titles, Navigation
*/
^ permalink raw reply [nested|flat] 290+ messages in thread
* Re: Another modest proposal for docs formatting: catalog descriptions
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 05:52 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-05 14:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 23:42 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-06 00:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:18 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
2020-05-06 21:24 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:38 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
2020-05-06 22:56 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-08 22:53 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-09 06:54 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-09 15:34 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-09 17:30 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-09 18:09 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-10 16:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
@ 2020-05-10 18:03 ` Jonathan S. Katz <[email protected]>
2020-05-14 02:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
0 siblings, 1 reply; 290+ messages in thread
From: Jonathan S. Katz @ 2020-05-10 18:03 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Fabien COELHO <[email protected]>; Alvaro Herrera <[email protected]>; [email protected]; PostgreSQL Developers <[email protected]>
On 5/10/20 12:27 PM, Tom Lane wrote:
> Just FTR, here's a complete patch for this.
Cool. I'll play around with it tonight once I clear out release work.
Per upthread reference, I believe you've become a CSS maven yourself.
> I successfully regenerated
> the column names, types, and ordering from the system catalogs, and
> plugged the descriptions back into that by dint of parsing them out of
> the XML. The "references" data was based on findoidjoins' results plus
> hand additions to cover all the cases we are calling out now (there are
> a lot of "references" links for attnums and a few other non-OID columns,
> plus references links for views which findoidjoins doesn't consider).
> So I have pretty high confidence that this info is OK. I'd be too
> embarrassed to show anybody the code though ;-) ... it was just a brute
> force hack.
If it works it works ;)
Jonathan
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 290+ messages in thread
* Re: Another modest proposal for docs formatting: catalog descriptions
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 05:52 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-05 14:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 23:42 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-06 00:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:18 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
2020-05-06 21:24 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:38 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
2020-05-06 22:56 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-08 22:53 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-09 06:54 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-09 15:34 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-09 17:30 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-09 18:09 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-10 16:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-10 18:03 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
@ 2020-05-14 02:27 ` Jonathan S. Katz <[email protected]>
2020-05-14 02:52 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
0 siblings, 1 reply; 290+ messages in thread
From: Jonathan S. Katz @ 2020-05-14 02:27 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Fabien COELHO <[email protected]>; Alvaro Herrera <[email protected]>; [email protected]; PostgreSQL Developers <[email protected]>
On 5/10/20 2:03 PM, Jonathan S. Katz wrote:
> On 5/10/20 12:27 PM, Tom Lane wrote:
>> Just FTR, here's a complete patch for this.
>
> Cool. I'll play around with it tonight once I clear out release work.
> Per upthread reference, I believe you've become a CSS maven yourself.
Time slipped a bit (sorry!), but while prepping for the release I
reviewed this. Visually, it looks WAY better. The code checks out too. I
think any tweaks would be primarily around personal preference on the
UI, so it may be better just to commit and get it out in the wild.
...and so I did. Committed[1].
Jonathan
[1]
https://git.postgresql.org/gitweb/?p=pgweb.git;a=commitdiff;h=93716f2a817dbdae8cccf86bc951b45b68ea52...
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 290+ messages in thread
* Re: Another modest proposal for docs formatting: catalog descriptions
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 05:52 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-05 14:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 23:42 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-06 00:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:18 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
2020-05-06 21:24 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:38 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
2020-05-06 22:56 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-08 22:53 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-09 06:54 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-09 15:34 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-09 17:30 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-09 18:09 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-10 16:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-10 18:03 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-14 02:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
@ 2020-05-14 02:52 ` Tom Lane <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Tom Lane @ 2020-05-14 02:52 UTC (permalink / raw)
To: Jonathan S. Katz <[email protected]>; +Cc: Fabien COELHO <[email protected]>; Alvaro Herrera <[email protected]>; [email protected]; PostgreSQL Developers <[email protected]>
"Jonathan S. Katz" <[email protected]> writes:
> Time slipped a bit (sorry!), but while prepping for the release I
> reviewed this. Visually, it looks WAY better. The code checks out too. I
> think any tweaks would be primarily around personal preference on the
> UI, so it may be better just to commit and get it out in the wild.
> ...and so I did. Committed[1].
Thanks, I'll push the docs change in a bit.
regards, tom lane
^ permalink raw reply [nested|flat] 290+ messages in thread
* Re: Another modest proposal for docs formatting: catalog descriptions
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 05:52 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-05 14:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 23:42 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-06 00:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:18 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
2020-05-06 21:24 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
2020-05-06 21:38 ` Re: Another modest proposal for docs formatting: catalog descriptions Alvaro Herrera <[email protected]>
@ 2020-05-18 20:11 ` Alvaro Herrera <[email protected]>
1 sibling, 0 replies; 290+ messages in thread
From: Alvaro Herrera @ 2020-05-18 20:11 UTC (permalink / raw)
To: Jonathan S. Katz <[email protected]>; +Cc: Tom Lane <[email protected]>; Fabien COELHO <[email protected]>; [email protected]; PostgreSQL Developers <[email protected]>
On 2020-May-06, Alvaro Herrera wrote:
> ... oh, okay. I guess I was reporting that the font on the new version
> seems to have got smaller. Looking at other pages, it appears that the
> font is indeed a lot smaller in all tables, including those Tom has been
> editing. So maybe this is desirable for some reason. I'll have to keep
> my magnifying glass handy, I suppose.
I happened to notice that the font used in the tables get smaller if you
make the browser window narrower. So what was happening is that I was
using a window that didn't cover the entire screen.
If I let the window use my whole screen, the font in the table is the
same size than the text outside the table; if I reduce to ~1239 pixels,
the font becomes somewhat smaller; if I further reduce to ~953 pixels,
it gets really small. Meanwhile, the non-table text keeps the same size
the whole time. (The pixel sizes at which changes occur seem to vary
with the zoom percentage I use, but the behavior is always there.)
Is this something that CSS does somehow? Is this something you expected?
Happens with both Brave (a Chromium derivate) and Firefox.
--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
^ permalink raw reply [nested|flat] 290+ messages in thread
* Re: Another modest proposal for docs formatting: catalog descriptions
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 05:52 ` Re: Another modest proposal for docs formatting: catalog descriptions Fabien COELHO <[email protected]>
2020-05-05 14:27 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
@ 2020-05-06 05:52 ` Fabien COELHO <[email protected]>
1 sibling, 0 replies; 290+ messages in thread
From: Fabien COELHO @ 2020-05-06 05:52 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: [email protected]; PostgreSQL Developers <[email protected]>
Hello Tom,
>> oid OID
>
> Meh. I'm not a fan of overuse of upper case --- it's well established
> that that's harder to read than lower or mixed case. And it's definitely
> project policy that type names are generally treated as identifiers not
> keywords, even if some of them happen to be keywords under the hood.
I found "oid oid" stuttering kind of strange, hence an attempt at
suggesting something that could distinguish them.
> The markup I had in mind was <structfield> for the field name
> and <type> for the type name, but no decoration beyond that.
Ok. If they are displayed a little differently afterwards that'd may help.
> As for the references, it seems to me that your notation would lead
> people to think that there are actual FK constraints in place, which
> of course there are not (especially not on the views).
In practice the system ensures that the target exists, so it is as-if
there would be a foreign key enforced?
My point is that using differing syntaxes for the more-or-less the same
concept does not help user understand the semantics, but maybe that is
just me.
> I'm not hugely against it but I prefer what I suggested.
Ok!
--
Fabien.
^ permalink raw reply [nested|flat] 290+ messages in thread
* Re: Another modest proposal for docs formatting: catalog descriptions
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
@ 2020-06-01 22:40 ` Josef Šimánek <[email protected]>
2020-06-01 22:57 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2 siblings, 1 reply; 290+ messages in thread
From: Josef Šimánek @ 2020-06-01 22:40 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: [email protected]; [email protected]
út 2. 6. 2020 v 0:30 odesílatel Tom Lane <[email protected]> napsal:
> As of HEAD, building the PDF docs for A4 paper draws 538 "contents
> ... exceed the available area" warnings. While this is a nice step
> forward from where we were (v12 has more than 1500 such warnings),
> we're far from done fixing that issue.
>
> A large chunk of the remaining warnings are about tables that describe
> the columns of system catalogs, system views, and information_schema
> views. The typical contents of a row in such a table are a field name,
> a field data type, possibly a "references" link, and then a description.
> Unsurprisingly, this does not work very well for descriptions of more
> than a few words. And not infrequently, we *need* more than a few words.
>
> ISTM this is more or less the same problem we have/had with function
> descriptions, and so I'm tempted to solve it in more or less the same
> way. Let's redefine the table layout to look like, say, this for
> pg_attrdef [1]:
>
> oid oid
> Row identifier
>
> adrelid oid (references pg_class.oid)
> The table this column belongs to
>
> adnum int2 (references pg_attribute.attnum)
> The number of the column
>
> adbin pg_node_tree
> The column default value, in nodeToString() representation. Use
> pg_get_expr(adbin, adrelid) to convert it to an SQL expression.
>
> That is, let's go over to something that's more or less like a table-ized
> <variablelist>, with the fixed items for an entry all written on the first
> line, and then as much description text as we need. The actual markup
> would be closely modeled on what we did for function-table entries.
>
> Thoughts?
>
I have spotted this change recently at progress monitoring devel docs (
https://www.postgresql.org/docs/devel/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING).
Current version seems a little chaotic since there are multiple tables on
the same page with 2 mixed layouts. Older layout (for example v12 one -
https://www.postgresql.org/docs/12/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING)
is much easier to read for me.
Is this final change? I do not see any problem on this (progress
monitoring) page in old layout. Is there any example of problematic page?
Maybe there's a different way to solve this. For example instead of
in-lining long text as a column description, it should be possible to link
to detailed description in custom paragraph or table. See description
column at table 27.22. at progress monitoring page for column "phase" for
similar approach.
>
> regards, tom lane
>
> [1] https://www.postgresql.org/docs/devel/catalog-pg-attrdef.html
>
>
>
>
>
^ permalink raw reply [nested|flat] 290+ messages in thread
* Re: Another modest proposal for docs formatting: catalog descriptions
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-06-01 22:40 ` Re: Another modest proposal for docs formatting: catalog descriptions Josef Šimánek <[email protected]>
@ 2020-06-01 22:57 ` Tom Lane <[email protected]>
2020-06-01 23:17 ` Re: Another modest proposal for docs formatting: catalog descriptions Jonathan S. Katz <[email protected]>
0 siblings, 1 reply; 290+ messages in thread
From: Tom Lane @ 2020-06-01 22:57 UTC (permalink / raw)
To: Josef Šimánek <[email protected]>; +Cc: [email protected]; [email protected]
=?UTF-8?B?Sm9zZWYgxaBpbcOhbmVr?= <[email protected]> writes:
> I have spotted this change recently at progress monitoring devel docs (
> https://www.postgresql.org/docs/devel/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING).
> Current version seems a little chaotic since there are multiple tables on
> the same page with 2 mixed layouts. Older layout (for example v12 one -
> https://www.postgresql.org/docs/12/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING)
> is much easier to read for me.
> Is this final change? I do not see any problem on this (progress
> monitoring) page in old layout. Is there any example of problematic page?
> Maybe there's a different way to solve this. For example instead of
> in-lining long text as a column description, it should be possible to link
> to detailed description in custom paragraph or table. See description
> column at table 27.22. at progress monitoring page for column "phase" for
> similar approach.
I'm not planning on revisiting that work, no. And converting every
table/view description table into two (or more?) tables sure doesn't
sound like an improvement.
Perhaps there's a case for reformatting the phase-description tables
in the progress monitoring section to look more like the view tables.
(I hadn't paid much attention to them, since they weren't causing PDF
rendering problems.) On the other hand, you could argue that it's
good that they don't look like the view tables, since the info they
are presenting is fundamentally different. I don't honestly see much
wrong with the way it is now.
regards, tom lane
^ permalink raw reply [nested|flat] 290+ messages in thread
* Re: Another modest proposal for docs formatting: catalog descriptions
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-06-01 22:40 ` Re: Another modest proposal for docs formatting: catalog descriptions Josef Šimánek <[email protected]>
2020-06-01 22:57 ` Re: Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
@ 2020-06-01 23:17 ` Jonathan S. Katz <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Jonathan S. Katz @ 2020-06-01 23:17 UTC (permalink / raw)
To: Tom Lane <[email protected]>; Josef Šimánek <[email protected]>; +Cc: [email protected]; [email protected]
On 6/1/20 6:57 PM, Tom Lane wrote:
> =?UTF-8?B?Sm9zZWYgxaBpbcOhbmVr?= <[email protected]> writes:
>> I have spotted this change recently at progress monitoring devel docs (
>> https://www.postgresql.org/docs/devel/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING).
>> Current version seems a little chaotic since there are multiple tables on
>> the same page with 2 mixed layouts. Older layout (for example v12 one -
>> https://www.postgresql.org/docs/12/progress-reporting.html#CREATE-INDEX-PROGRESS-REPORTING)
>> is much easier to read for me.
>
>> Is this final change? I do not see any problem on this (progress
>> monitoring) page in old layout. Is there any example of problematic page?
>> Maybe there's a different way to solve this. For example instead of
>> in-lining long text as a column description, it should be possible to link
>> to detailed description in custom paragraph or table. See description
>> column at table 27.22. at progress monitoring page for column "phase" for
>> similar approach.
>
> I'm not planning on revisiting that work, no. And converting every
> table/view description table into two (or more?) tables sure doesn't
> sound like an improvement.
>
> Perhaps there's a case for reformatting the phase-description tables
> in the progress monitoring section to look more like the view tables.
> (I hadn't paid much attention to them, since they weren't causing PDF
> rendering problems.) On the other hand, you could argue that it's
> good that they don't look like the view tables, since the info they
> are presenting is fundamentally different. I don't honestly see much
> wrong with the way it is now.
I think it looks fine. +1 for leaving it.
Jonathan
Attachments:
[application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt
@ 2025-08-28 17:49 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 290+ messages in thread
From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw)
---
src/backend/commands/statscmds.c | 26 +++++-----------
src/backend/commands/tablecmds.c | 4 +--
src/backend/parser/parse_utilcmd.c | 13 +++++---
src/backend/tcop/utility.c | 48 +++++++++++++++++-------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 49 insertions(+), 46 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index e24d540cd45..07b2b5bfef3 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b)
* CREATE STATISTICS
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, List *relids)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt)
ListCell *cell;
ListCell *cell2;
- Assert(IsA(stmt, CreateStatsStmt));
-
/*
* Examine the FROM clause. Currently, we only allow it to be a single
* simple table, but later we'll probably allow multiple tables and JOIN
- * syntax. The grammar is already prepared for that, so we have to check
- * here that what we got is what we can support.
+ * syntax. Parse analysis checked the list length already, so this is
+ * just defense-in-depth.
*/
- if (list_length(stmt->relations) != 1)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ Assert(list_length(stmt->relations) == list_length(relids));
+ if (list_length(relids) != 1)
+ elog(ERROR, "only a single relation is allowed in CREATE STATISTICS");
- foreach(cell, stmt->relations)
+ foreach_oid(relid, relids)
{
- Node *rln = (Node *) lfirst(cell);
-
- if (!IsA(rln, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
-
/*
* CREATE STATISTICS will influence future execution plans but does
* not interfere with currently executing plans. So it should be
@@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt)
* conflicting with ANALYZE and other DDL that sets statistical
* information, but not with normal queries.
*/
- rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
/* Restrict to allowed relation types */
if (rel->rd_rel->relkind != RELKIND_RELATION &&
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 082a3575d62..1779bae80cb 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
/* The CreateStatsStmt has already been through transformStatsStmt */
Assert(stmt->transformed);
- address = CreateStatistics(stmt);
+ address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel)));
return address;
}
@@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
}
else if (IsA(stmt, CreateStatsStmt))
querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
+ transformStatsStmt(list_make1_oid(oldRelId),
(CreateStatsStmt *) stmt,
cmd));
else
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index afcf54169c3..587e2ef439c 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in relids list (and not on stmt->relations) to determine the
+ * target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
@@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
if (stmt->transformed)
return stmt;
+ if (list_length(relids) != 1)
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("only a single relation is allowed in CREATE STATISTICS"));
+
/* Set up pstate */
pstate = make_parsestate(NULL);
pstate->p_sourcetext = queryString;
@@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
* to its fields without qualification. Caller is responsible for locking
* relation, but we still need to open it.
*/
- rel = relation_open(relid, NoLock);
+ rel = relation_open(linitial_oid(relids), NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4f4191b0ea6..e09da051310 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate,
case T_CreateStatsStmt:
{
- Oid relid;
CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
+ List *relids = NIL;
+ ListCell *cell;
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ foreach(cell, stmt->relations)
+ {
+ Oid relid;
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
+ if (!IsA(lfirst(cell), RangeVar))
+ ereport(ERROR,
+ errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create statistics on specified relation"),
+ errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables."));
+ /*
+ * CREATE STATISTICS will influence future execution
+ * plans but does not interfere with currently
+ * executing plans. So it should be enough to take
+ * ShareUpdateExclusiveLock on relation, conflicting
+ * with ANALYZE and other DDL that sets statistical
+ * information, but not with normal queries.
+ *
+ * XXX RangeVarCallbackOwnsRelation not needed here,
+ * to keep the same behavior as before.
+ */
+ relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)),
+ ShareUpdateExclusiveLock, false);
+ relids = lappend_oid(relids, relid);
+ }
/* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
+ stmt = transformStatsStmt(relids, stmt, queryString);
- address = CreateStatistics(stmt);
+ /* ... and execute the command */
+ address = CreateStatistics(stmt, relids);
}
break;
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..7b8cb40cd83 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 9f2b58de797..28165eb1198 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.39.5
--pbl4mx6qlbtkw6pf
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
filename="0001-CREATE-STATISTICS-Fix-error-message.patch"
^ permalink raw reply [nested|flat] 290+ messages in thread
end of thread, other threads:[~2025-08-28 17:49 UTC | newest]
Thread overview: 290+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-05-05 01:52 Another modest proposal for docs formatting: catalog descriptions Tom Lane <[email protected]>
2020-05-05 02:12 ` Jonathan S. Katz <[email protected]>
2020-05-05 05:52 ` Fabien COELHO <[email protected]>
2020-05-05 14:27 ` Tom Lane <[email protected]>
2020-05-05 23:42 ` Tom Lane <[email protected]>
2020-05-06 00:27 ` Jonathan S. Katz <[email protected]>
2020-05-06 21:18 ` Alvaro Herrera <[email protected]>
2020-05-06 21:24 ` Jonathan S. Katz <[email protected]>
2020-05-06 21:38 ` Alvaro Herrera <[email protected]>
2020-05-06 22:56 ` Tom Lane <[email protected]>
2020-05-08 22:53 ` Tom Lane <[email protected]>
2020-05-09 06:54 ` Fabien COELHO <[email protected]>
2020-05-09 15:34 ` Tom Lane <[email protected]>
2020-05-09 17:30 ` Fabien COELHO <[email protected]>
2020-05-09 18:09 ` Tom Lane <[email protected]>
2020-05-10 16:27 ` Tom Lane <[email protected]>
2020-05-10 18:03 ` Jonathan S. Katz <[email protected]>
2020-05-14 02:27 ` Jonathan S. Katz <[email protected]>
2020-05-14 02:52 ` Tom Lane <[email protected]>
2020-05-18 20:11 ` Alvaro Herrera <[email protected]>
2020-05-06 05:52 ` Fabien COELHO <[email protected]>
2020-06-01 22:40 ` Josef Šimánek <[email protected]>
2020-06-01 22:57 ` Tom Lane <[email protected]>
2020-06-01 23:17 ` Jonathan S. Katz <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]>
2025-08-28 17:49 [PATCH] 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