From c1f364e4a64fec0a7acb9acf3da5acdd2d21789e Mon Sep 17 00:00:00 2001
From: David Rowley <dgrowley@gmail.com>
Date: Sun, 22 Sep 2024 23:56:39 +1200
Subject: [PATCH v8 2/2] fixup! Implementation of the 'ONLY' feature for
 ANALYZE and VACUUM.

---
 doc/src/sgml/ddl.sgml                | 21 +++++-----
 doc/src/sgml/monitoring.sgml         |  2 +-
 doc/src/sgml/ref/analyze.sgml        | 36 ++++++++--------
 doc/src/sgml/ref/vacuum.sgml         | 13 +++---
 src/backend/commands/vacuum.c        | 12 +++---
 src/test/regress/expected/vacuum.out | 60 ++++++++++++++++-----------
 src/test/regress/sql/vacuum.sql      | 62 +++++++++++++++++-----------
 7 files changed, 116 insertions(+), 90 deletions(-)

diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index c7de29ae71..8ab0ddb112 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -3778,14 +3778,14 @@ VALUES ('Albany', NULL, NULL, 'NY');
    not <literal>INSERT</literal> or <literal>ALTER TABLE ...
    RENAME</literal>) typically default to including child tables and
    support the <literal>ONLY</literal> notation to exclude them.
-   Some commands that do database maintenance and tuning
+   The majority of commands that do database maintenance and tuning
    (e.g., <literal>REINDEX</literal>) only work on individual, physical
-   tables and do not support recursing over inheritance hierarchies,
-   while other maintenance commands (e.g., <literal>VACUUM</literal>,
-   <literal>ANALYZE</literal>) default to including child tables
-   and support the <literal>ONLY</literal> notation to exclude them.
-   The respective behavior of each individual command is documented
-   in its reference page (<xref linkend="sql-commands"/>).
+   tables and do not support recursing over inheritance hierarchies.
+   However, both <literal>VACUUM</literal> and <literal>ANALYZE</literal>
+   commands default to including child tables and the <literal>ONLY</literal>
+   notation is supported to allow them to be excluded.  The respective
+   behavior of each individual command is documented in its reference page
+   (<xref linkend="sql-commands"/>).
   </para>
 
   <para>
@@ -4856,9 +4856,10 @@ ALTER TABLE measurement_y2008m02 INHERIT measurement;
 
       <listitem>
        <para>
-        Manual <command>VACUUM</command> or <command>ANALYZE</command> commands
-        will automatically process all descendant tables. If this is undesirable,
-        you can use the <literal>ONLY</literal> keyword. A command like:
+        Manual <command>VACUUM</command> and <command>ANALYZE</command>
+        commands will automatically process all inheritance child tables.  If
+        this is undesirable, you can use the <literal>ONLY</literal> keyword.
+        A command like:
 <programlisting>
 ANALYZE ONLY measurement;
 </programlisting>
diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 82d6aef858..db7f35a451 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -5527,7 +5527,7 @@ FROM pg_stat_get_backend_idset() AS backendid;
    <para>
     Note that when <command>ANALYZE</command> is run on a partitioned table
     without the <literal>ONLY</literal> keyword, all of its partitions are
-    also recursively analyzed. In that case, <command>ANALYZE</command>
+    also recursively analyzed.  In that case, <command>ANALYZE</command>
     progress is reported first for the parent table, whereby its inheritance
     statistics are collected, followed by that for each partition.
    </para>
diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml
index 92e465ca59..a0db56ae74 100644
--- a/doc/src/sgml/ref/analyze.sgml
+++ b/doc/src/sgml/ref/analyze.sgml
@@ -142,12 +142,12 @@ ANALYZE [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ <r
       The name (possibly schema-qualified) of a specific table to
       analyze.  If omitted, all regular tables, partitioned tables, and
       materialized views in the current database are analyzed (but not
-      foreign tables). If <literal>ONLY</literal> is specified before
-      the table name, only that table is analyzed. If <literal>ONLY</literal>
-      is not specified, the table and all its descendant tables or partitions
-      (if any) are analyzed.  Optionally, <literal>*</literal>
+      foreign tables).  If <literal>ONLY</literal> is specified before
+      the table name, only that table is analyzed.  If <literal>ONLY</literal>
+      is not specified, the table and all its inheritance child tables or
+      partitions (if any) are analyzed.  Optionally, <literal>*</literal>
       can be specified after the table name to explicitly indicate that
-      descendant tables are included.
+      inheritance child tables (or partitions) are to be analyzed.
      </para>
     </listitem>
    </varlistentry>
@@ -287,23 +287,23 @@ ANALYZE [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ <r
     <command>ANALYZE</command> gathers two sets of statistics: one on the rows
     of the parent table only, and a second including rows of both the parent
     table and all of its children.  This second set of statistics is needed when
-    planning queries that process the inheritance tree as a whole.  If the
-    <literal>ONLY</literal> keyword is used, child tables themselves are not
-    individually analyzed. The autovacuum daemon, however, will only consider
-    inserts or updates on the parent table itself when deciding whether to
-    trigger an automatic analyze for that table.  If that table is rarely
-    inserted into or updated, the inheritance statistics will not be up to
-    date unless you run <command>ANALYZE</command> manually.
+    planning queries that process the inheritance tree as a whole.  The
+    autovacuum daemon, however, will only consider inserts or updates on the
+    parent table itself when deciding whether to trigger an automatic analyze
+    for that table.  If that table is rarely inserted into or updated, the
+    inheritance statistics will not be up to date unless you run
+    <command>ANALYZE</command> manually.  By default,
+    <command>ANALYZE</command> will also recursively collect and update the
+    statistics for each inheritance child table.  The <literal>ONLY</literal>
+    keyword may be used to disable this.
   </para>
 
   <para>
     For partitioned tables, <command>ANALYZE</command> gathers statistics by
-    sampling rows from all partitions. If <literal>ONLY</literal> is not
-    specified, it will also recurse into each partition and update its
-    statistics.  Each leaf partition is analyzed only once, even with
-    multi-level partitioning.  No statistics are collected for only the
-    parent table (without data from its partitions), because with
-    partitioning it is guaranteed to be empty.
+    sampling rows from all partitions.  By default,
+    <command>ANALYZE</command> will also recursively collect and update the
+    statistics for each partition.  The <literal>ONLY</literal> keyword may be
+    used to disable this.
   </para>
 
   <para>
diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml
index 465f9fecf7..9110938fab 100644
--- a/doc/src/sgml/ref/vacuum.sgml
+++ b/doc/src/sgml/ref/vacuum.sgml
@@ -401,12 +401,13 @@ VACUUM [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] [ <re
     <listitem>
      <para>
       The name (optionally schema-qualified) of a specific table or
-      materialized view to vacuum.  If <literal>ONLY</literal> is specified before
-      the table name, only that table is vacuumed. If <literal>ONLY</literal>
-      is not specified, the table and all its descendant tables or partitions
-      (if any) are also vacuumed.  Optionally, <literal>*</literal>
-      can be specified after the table name to explicitly indicate that
-      descendant tables are included.
+      materialized view to vacuum.  If <literal>ONLY</literal> is specified
+      before the table name, only that table is vacuumed.  If
+      <literal>ONLY</literal> is not specified, the table and all its
+      inheritance child tables or partitions (if any) are also vacuumed.
+      Optionally, <literal>*</literal> can be specified after the table name
+      to explicitly indicate that inheritance child tables (or partitions) are
+      to be vacuumed.
      </para>
     </listitem>
    </varlistentry>
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 951e00acee..bcadca4214 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -851,8 +851,7 @@ vacuum_open_relation(Oid relid, RangeVar *relation, bits32 options,
 
 /*
  * Given a VacuumRelation, fill in the table OID if it wasn't specified,
- * and optionally add VacuumRelations for partitions or descendant tables
- * of the table.
+ * and optionally add VacuumRelations for partitions or inheritance children.
  *
  * If a VacuumRelation does not have an OID supplied and is a partitioned
  * table, an extra entry will be added to the output for each partition.
@@ -881,8 +880,8 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context,
 	else
 	{
 		/*
-		 * Process a specific relation, and possibly partitions and/or child
-		 * tables thereof
+		 * Process a specific relation, and possibly partitions or child
+		 * tables thereof.
 		 */
 		Oid			relid;
 		HeapTuple	tuple;
@@ -951,7 +950,7 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context,
 
 		/*
 		 * Vacuuming a partitioned table with ONLY will not do anything since
-		 * the partitioned table itself is empty. Issue a warning if the user
+		 * the partitioned table itself is empty.  Issue a warning if the user
 		 * requests this.
 		 */
 		include_children = vrel->relation->inh;
@@ -961,12 +960,11 @@ expand_vacuum_rel(VacuumRelation *vrel, MemoryContext vac_context,
 					(errmsg("VACUUM ONLY of partitioned table \"%s\" has no effect",
 							vrel->relation->relname)));
 
-
 		ReleaseSysCache(tuple);
 
 		/*
 		 * Unless the user has specified ONLY, make relation list entries for
-		 * its partitions and/or descendant tables.  Note that the list
+		 * its partitions or inheritance child tables.  Note that the list
 		 * returned by find_all_inheritors() includes the passed-in OID, so we
 		 * have to skip that.  There's no point in taking locks on the
 		 * individual partitions/tables yet, and doing so would just add
diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out
index 7165235ece..1a07dbf67d 100644
--- a/src/test/regress/expected/vacuum.out
+++ b/src/test/regress/expected/vacuum.out
@@ -291,14 +291,17 @@ ANALYZE vactst, vactst;
 BEGIN;  -- ANALYZE behaves differently inside a transaction block
 ANALYZE vactst, vactst;
 COMMIT;
--- ANALYZE ONLY / VACUUM ONLY on partitioned table
-CREATE TABLE only_parted (a int, b char) PARTITION BY LIST (a);
+--
+-- Tests for ANALYZE ONLY / VACUUM ONLY on partitioned tables
+--
+CREATE TABLE only_parted (a int, b text) PARTITION BY LIST (a);
 CREATE TABLE only_parted1 PARTITION OF only_parted FOR VALUES IN (1);
 INSERT INTO only_parted VALUES (1, 'a');
--- Only partitioned table is analyzed
+-- Ensure only the partitioned table is analyzed
 ANALYZE ONLY only_parted;
-SELECT relname, last_analyze is not null as analyzed, last_vacuum is not null as vacuumed FROM pg_stat_user_tables
-  WHERE relname like 'only_parted%'
+SELECT relname, last_analyze IS NOT NULL AS analyzed, last_vacuum IS NOT NULL AS vacuumed
+  FROM pg_stat_user_tables
+  WHERE relid IN ('only_parted'::regclass, 'only_parted1'::regclass)
   ORDER BY relname;
    relname    | analyzed | vacuumed 
 --------------+----------+----------
@@ -306,10 +309,11 @@ SELECT relname, last_analyze is not null as analyzed, last_vacuum is not null as
  only_parted1 | f        | f
 (2 rows)
 
--- Partitioned table and partitions are analyzed
+-- Ensure partitioned table and the partitions are analyzed
 ANALYZE only_parted;
-SELECT relname, last_analyze is not null as analyzed, last_vacuum is not null as vacuumed FROM pg_stat_user_tables
-  WHERE relname like 'only_parted%'
+SELECT relname, last_analyze IS NOT NULL AS analyzed, last_vacuum IS NOT NULL AS vacuumed
+  FROM pg_stat_user_tables
+  WHERE relid IN ('only_parted'::regclass, 'only_parted1'::regclass)
   ORDER BY relname;
    relname    | analyzed | vacuumed 
 --------------+----------+----------
@@ -317,17 +321,23 @@ SELECT relname, last_analyze is not null as analyzed, last_vacuum is not null as
  only_parted1 | t        | f
 (2 rows)
 
-VACUUM ONLY vacparted; -- gives warning
+DROP TABLE only_parted;
+-- VACUUM ONLY on a partitioned table does nothing, ensure we get a warning.
+VACUUM ONLY vacparted;
 WARNING:  VACUUM ONLY of partitioned table "vacparted" has no effect
-ANALYZE ONLY vacparted(a,b); -- combine ONLY with column list
--- ANALYZE ONLY on inherited tables
+-- Try ANALYZE ONLY with a column list
+ANALYZE ONLY vacparted(a,b);
+--
+-- Tests for VACUUM ONLY / ANALYZE ONLY on inheritance tables
+--
 CREATE TABLE only_inh_parent (a int primary key, b TEXT);
 CREATE TABLE only_inh_child () INHERITS (only_inh_parent);
 INSERT INTO only_inh_child(a,b) VALUES (1, 'aaa'), (2, 'bbb'), (3, 'ccc');
--- Only parent is ANALYZED
+-- Ensure only parent is analyzed
 ANALYZE ONLY only_inh_parent;
-SELECT relname, last_analyze is not null as analyzed, last_vacuum is not null as vacuumed FROM pg_stat_user_tables
-  WHERE relname like 'only_inh%'
+SELECT relname, last_analyze IS NOT NULL AS analyzed, last_vacuum IS NOT NULL AS vacuumed
+  FROM pg_stat_user_tables
+  WHERE relid IN ('only_inh_parent'::regclass, 'only_inh_child'::regclass)
   ORDER BY relname;
      relname     | analyzed | vacuumed 
 -----------------+----------+----------
@@ -335,10 +345,11 @@ SELECT relname, last_analyze is not null as analyzed, last_vacuum is not null as
  only_inh_parent | t        | f
 (2 rows)
 
--- Parent and child are ANALYZED
+-- Ensure the parent and child are analyzed
 ANALYZE only_inh_parent;
-SELECT relname, last_analyze is not null as analyzed, last_vacuum is not null as vacuumed FROM pg_stat_user_tables
-  WHERE relname like 'only_inh%'
+SELECT relname, last_analyze IS NOT NULL AS analyzed, last_vacuum IS NOT NULL AS vacuumed
+  FROM pg_stat_user_tables
+  WHERE relid IN ('only_inh_parent'::regclass, 'only_inh_child'::regclass)
   ORDER BY relname;
      relname     | analyzed | vacuumed 
 -----------------+----------+----------
@@ -346,10 +357,11 @@ SELECT relname, last_analyze is not null as analyzed, last_vacuum is not null as
  only_inh_parent | t        | f
 (2 rows)
 
--- Only parent is VACUUMED
+-- Ensure only the parent is vacuumed
 VACUUM ONLY only_inh_parent;
-SELECT relname, last_analyze is not null as analyzed, last_vacuum is not null as vacuumed FROM pg_stat_user_tables
-  WHERE relname like 'only_inh%'
+SELECT relname, last_analyze IS NOT NULL AS analyzed, last_vacuum IS NOT NULL AS vacuumed
+  FROM pg_stat_user_tables
+  WHERE relid IN ('only_inh_parent'::regclass, 'only_inh_child'::regclass)
   ORDER BY relname;
      relname     | analyzed | vacuumed 
 -----------------+----------+----------
@@ -357,10 +369,11 @@ SELECT relname, last_analyze is not null as analyzed, last_vacuum is not null as
  only_inh_parent | t        | t
 (2 rows)
 
--- Parent and child are VACUUMED
+-- Ensure parent and child are vacuumed
 VACUUM only_inh_parent;
-SELECT relname, last_analyze is not null as analyzed, last_vacuum is not null as vacuumed FROM pg_stat_user_tables
-  WHERE relname like 'only_inh%'
+SELECT relname, last_analyze IS NOT NULL AS analyzed, last_vacuum IS NOT NULL AS vacuumed
+  FROM pg_stat_user_tables
+  WHERE relid IN ('only_inh_parent'::regclass, 'only_inh_child'::regclass)
   ORDER BY relname;
      relname     | analyzed | vacuumed 
 -----------------+----------+----------
@@ -368,7 +381,6 @@ SELECT relname, last_analyze is not null as analyzed, last_vacuum is not null as
  only_inh_parent | t        | t
 (2 rows)
 
-DROP TABLE only_parted CASCADE;
 DROP TABLE only_inh_parent CASCADE;
 NOTICE:  drop cascades to table only_inh_child
 -- parenthesized syntax for ANALYZE
diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql
index fc84df0f07..5e55079e71 100644
--- a/src/test/regress/sql/vacuum.sql
+++ b/src/test/regress/sql/vacuum.sql
@@ -233,56 +233,70 @@ BEGIN;  -- ANALYZE behaves differently inside a transaction block
 ANALYZE vactst, vactst;
 COMMIT;
 
--- ANALYZE ONLY / VACUUM ONLY on partitioned table
-CREATE TABLE only_parted (a int, b char) PARTITION BY LIST (a);
+--
+-- Tests for ANALYZE ONLY / VACUUM ONLY on partitioned tables
+--
+CREATE TABLE only_parted (a int, b text) PARTITION BY LIST (a);
 CREATE TABLE only_parted1 PARTITION OF only_parted FOR VALUES IN (1);
 INSERT INTO only_parted VALUES (1, 'a');
 
--- Only partitioned table is analyzed
+-- Ensure only the partitioned table is analyzed
 ANALYZE ONLY only_parted;
-SELECT relname, last_analyze is not null as analyzed, last_vacuum is not null as vacuumed FROM pg_stat_user_tables
-  WHERE relname like 'only_parted%'
+SELECT relname, last_analyze IS NOT NULL AS analyzed, last_vacuum IS NOT NULL AS vacuumed
+  FROM pg_stat_user_tables
+  WHERE relid IN ('only_parted'::regclass, 'only_parted1'::regclass)
   ORDER BY relname;
 
--- Partitioned table and partitions are analyzed
+-- Ensure partitioned table and the partitions are analyzed
 ANALYZE only_parted;
-SELECT relname, last_analyze is not null as analyzed, last_vacuum is not null as vacuumed FROM pg_stat_user_tables
-  WHERE relname like 'only_parted%'
+SELECT relname, last_analyze IS NOT NULL AS analyzed, last_vacuum IS NOT NULL AS vacuumed
+  FROM pg_stat_user_tables
+  WHERE relid IN ('only_parted'::regclass, 'only_parted1'::regclass)
   ORDER BY relname;
 
-VACUUM ONLY vacparted; -- gives warning
-ANALYZE ONLY vacparted(a,b); -- combine ONLY with column list
+DROP TABLE only_parted;
+
+-- VACUUM ONLY on a partitioned table does nothing, ensure we get a warning.
+VACUUM ONLY vacparted;
 
--- ANALYZE ONLY on inherited tables
+-- Try ANALYZE ONLY with a column list
+ANALYZE ONLY vacparted(a,b);
+
+--
+-- Tests for VACUUM ONLY / ANALYZE ONLY on inheritance tables
+--
 CREATE TABLE only_inh_parent (a int primary key, b TEXT);
 CREATE TABLE only_inh_child () INHERITS (only_inh_parent);
 INSERT INTO only_inh_child(a,b) VALUES (1, 'aaa'), (2, 'bbb'), (3, 'ccc');
 
--- Only parent is ANALYZED
+-- Ensure only parent is analyzed
 ANALYZE ONLY only_inh_parent;
-SELECT relname, last_analyze is not null as analyzed, last_vacuum is not null as vacuumed FROM pg_stat_user_tables
-  WHERE relname like 'only_inh%'
+SELECT relname, last_analyze IS NOT NULL AS analyzed, last_vacuum IS NOT NULL AS vacuumed
+  FROM pg_stat_user_tables
+  WHERE relid IN ('only_inh_parent'::regclass, 'only_inh_child'::regclass)
   ORDER BY relname;
 
--- Parent and child are ANALYZED
+-- Ensure the parent and child are analyzed
 ANALYZE only_inh_parent;
-SELECT relname, last_analyze is not null as analyzed, last_vacuum is not null as vacuumed FROM pg_stat_user_tables
-  WHERE relname like 'only_inh%'
+SELECT relname, last_analyze IS NOT NULL AS analyzed, last_vacuum IS NOT NULL AS vacuumed
+  FROM pg_stat_user_tables
+  WHERE relid IN ('only_inh_parent'::regclass, 'only_inh_child'::regclass)
   ORDER BY relname;
 
--- Only parent is VACUUMED
+-- Ensure only the parent is vacuumed
 VACUUM ONLY only_inh_parent;
-SELECT relname, last_analyze is not null as analyzed, last_vacuum is not null as vacuumed FROM pg_stat_user_tables
-  WHERE relname like 'only_inh%'
+SELECT relname, last_analyze IS NOT NULL AS analyzed, last_vacuum IS NOT NULL AS vacuumed
+  FROM pg_stat_user_tables
+  WHERE relid IN ('only_inh_parent'::regclass, 'only_inh_child'::regclass)
   ORDER BY relname;
 
--- Parent and child are VACUUMED
+-- Ensure parent and child are vacuumed
 VACUUM only_inh_parent;
-SELECT relname, last_analyze is not null as analyzed, last_vacuum is not null as vacuumed FROM pg_stat_user_tables
-  WHERE relname like 'only_inh%'
+SELECT relname, last_analyze IS NOT NULL AS analyzed, last_vacuum IS NOT NULL AS vacuumed
+  FROM pg_stat_user_tables
+  WHERE relid IN ('only_inh_parent'::regclass, 'only_inh_child'::regclass)
   ORDER BY relname;
 
-DROP TABLE only_parted CASCADE;
 DROP TABLE only_inh_parent CASCADE;
 
 -- parenthesized syntax for ANALYZE
-- 
2.34.1

