On Fri, Jul 15, 2022 at 06:21:22PM +0100, Simon Riggs wrote:
> That's fixed it on the CFbot. Over to you, Michael. Thanks.

Sure.  I have looked over that, and this looks fine overall.  I have
made two changes though.

        if (objectKind == REINDEX_OBJECT_SYSTEM &&
-           !IsSystemClass(relid, classtuple))
+           !IsCatalogRelationOid(relid))
+           continue;
+       else if (objectKind == REINDEX_OBJECT_DATABASE &&
+                IsCatalogRelationOid(relid))

The patch originally relied on IsSystemClass() to decide if a relation
is a catalog table or not.  This is not wrong in itself because
ReindexMultipleTables() discards RELKIND_TOAST a couple of lines
above, but I think that we should switch to IsCatalogRelationOid() as
that's the line drawn to check for the catalog-ness of a relation.

The second thing is test coverage.  Using a REINDEX DATABASE/SYSTEM
within the main regression test suite is not a good idea, but we
already have those commands running in the reindexdb suite so I could
not resist expanding the test section to track and check relfilenode
changes through four relations for these cases:
- Catalog index.
- Catalog toast index.
- User table index.
- User toast index.
The relfilenodes of those relations are saved in a table and
cross-checked with the contents of pg_class after each REINDEX, on
SYSTEM or DATABASE.  There are no new heavy commands, so it does not
make the test longer.

With all that, I finish with the attached.  Does that look fine to
you?
--
Michael
From 6c9e6e88dd663f7341b38a787fee5c7a646ff022 Mon Sep 17 00:00:00 2001
From: Michael Paquier <mich...@paquier.xyz>
Date: Sun, 17 Jul 2022 15:00:17 +0900
Subject: [PATCH v6] Rework and add more stuff for REINDEX SYSTEM/DATABASE

---
 src/backend/commands/indexcmds.c   | 21 +++++--
 src/backend/parser/gram.y          | 25 ++++++++-
 src/bin/scripts/reindexdb.c        | 12 ----
 src/bin/scripts/t/090_reindexdb.pl | 88 +++++++++++++++++++++++-------
 doc/src/sgml/ref/reindex.sgml      | 12 ++--
 5 files changed, 114 insertions(+), 44 deletions(-)

diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index ff847579f3..15a57ea9c3 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2877,11 +2877,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	bool		concurrent_warning = false;
 	bool		tablespace_warning = false;
 
-	AssertArg(objectName);
 	Assert(objectKind == REINDEX_OBJECT_SCHEMA ||
 		   objectKind == REINDEX_OBJECT_SYSTEM ||
 		   objectKind == REINDEX_OBJECT_DATABASE);
 
+	/*
+	 * This matches the options enforced by the grammar, where the object name
+	 * is optional for DATABASE and SYSTEM.
+	 */
+	AssertArg(objectName || objectKind != REINDEX_OBJECT_SCHEMA);
+
 	if (objectKind == REINDEX_OBJECT_SYSTEM &&
 		(params->options & REINDEXOPT_CONCURRENTLY) != 0)
 		ereport(ERROR,
@@ -2906,13 +2911,13 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 	{
 		objectOid = MyDatabaseId;
 
-		if (strcmp(objectName, get_database_name(objectOid)) != 0)
+		if (objectName && strcmp(objectName, get_database_name(objectOid)) != 0)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("can only reindex the currently open database")));
 		if (!pg_database_ownercheck(objectOid, GetUserId()))
 			aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_DATABASE,
-						   objectName);
+						   get_database_name(objectOid));
 	}
 
 	/*
@@ -2970,9 +2975,15 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
-		/* Check user/system classification, and optionally skip */
+		/*
+		 * Check user/system classification.  SYSTEM processes all the
+		 * catalogs, and DATABASE processes everything that's not a catalog.
+		 */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
-			!IsSystemClass(relid, classtuple))
+			!IsCatalogRelationOid(relid))
+			continue;
+		else if (objectKind == REINDEX_OBJECT_DATABASE &&
+				 IsCatalogRelationOid(relid))
 			continue;
 
 		/*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c018140afe..df5ceea910 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -560,7 +560,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <defelt>	generic_option_elem alter_generic_option_elem
 %type <list>	generic_option_list alter_generic_option_list
 
-%type <ival>	reindex_target_type reindex_target_multitable
+%type <ival>	reindex_target_type reindex_target_multitable reindex_name_optional
 
 %type <node>	copy_generic_opt_arg copy_generic_opt_arg_list_item
 %type <defelt>	copy_generic_opt_elem
@@ -9115,6 +9115,24 @@ ReindexStmt:
 											makeDefElem("concurrently", NULL, @3));
 					$$ = (Node *) n;
 				}
+			| REINDEX reindex_name_optional
+				{
+					ReindexStmt *n = makeNode(ReindexStmt);
+					n->kind = $2;
+					n->name = NULL;
+					n->relation = NULL;
+					n->params = NIL;
+					$$ = (Node *)n;
+				}
+			| REINDEX '(' utility_option_list ')' reindex_name_optional
+				{
+					ReindexStmt *n = makeNode(ReindexStmt);
+					n->kind = $5;
+					n->name = NULL;
+					n->relation = NULL;
+					n->params = $3;
+					$$ = (Node *)n;
+				}
 			| REINDEX '(' utility_option_list ')' reindex_target_type opt_concurrently qualified_name
 				{
 					ReindexStmt *n = makeNode(ReindexStmt);
@@ -9151,6 +9169,11 @@ reindex_target_multitable:
 			| SYSTEM_P				{ $$ = REINDEX_OBJECT_SYSTEM; }
 			| DATABASE				{ $$ = REINDEX_OBJECT_DATABASE; }
 		;
+/* For these options the name is optional */
+reindex_name_optional:
+			SYSTEM_P				{ $$ = REINDEX_OBJECT_SYSTEM; }
+			| DATABASE				{ $$ = REINDEX_OBJECT_DATABASE; }
+		;
 
 /*****************************************************************************
  *
diff --git a/src/bin/scripts/reindexdb.c b/src/bin/scripts/reindexdb.c
index f3b03ec325..0e93a4eeff 100644
--- a/src/bin/scripts/reindexdb.c
+++ b/src/bin/scripts/reindexdb.c
@@ -360,18 +360,6 @@ reindex_one_database(ConnParams *cparams, ReindexType type,
 		{
 			case REINDEX_DATABASE:
 
-				/*
-				 * Database-wide parallel reindex requires special processing.
-				 * If multiple jobs were asked, we have to reindex system
-				 * catalogs first as they cannot be processed in parallel.
-				 */
-				if (concurrently)
-					pg_log_warning("cannot reindex system catalogs concurrently, skipping all");
-				else
-					run_reindex_command(conn, REINDEX_SYSTEM, PQdb(conn), echo,
-										verbose, concurrently, false,
-										tablespace);
-
 				/* Build a list of relations from the database */
 				process_list = get_parallel_object_list(conn, process_type,
 														user_list, echo);
diff --git a/src/bin/scripts/t/090_reindexdb.pl b/src/bin/scripts/t/090_reindexdb.pl
index 398fc4e6bb..f1e3c0fa21 100644
--- a/src/bin/scripts/t/090_reindexdb.pl
+++ b/src/bin/scripts/t/090_reindexdb.pl
@@ -25,11 +25,6 @@ my $tbspace_name = 'reindex_tbspace';
 $node->safe_psql('postgres',
 	"CREATE TABLESPACE $tbspace_name LOCATION '$tbspace_path';");
 
-$node->issues_sql_like(
-	[ 'reindexdb', 'postgres' ],
-	qr/statement: REINDEX DATABASE postgres;/,
-	'SQL REINDEX run');
-
 # Use text as data type to get a toast table.
 $node->safe_psql('postgres',
 	'CREATE TABLE test1 (a text); CREATE INDEX test1x ON test1 (a);');
@@ -40,6 +35,71 @@ my $toast_table = $node->safe_psql('postgres',
 my $toast_index = $node->safe_psql('postgres',
 	"SELECT indexrelid::regclass FROM pg_index WHERE indrelid = '$toast_table'::regclass;"
 );
+my $catalog_toast_index = $node->safe_psql('postgres',
+	"SELECT indexrelid::regclass FROM pg_index WHERE indrelid = '$toast_table'::regclass;"
+);
+
+# Set of SQL queries to cross-check the state of relfilenodes across
+# REINDEX operations.  A set of relfilenodes is saved from the catalogs
+# and then compared with pg_class.
+$node->safe_psql('postgres',
+	'CREATE TABLE toast_relfilenodes (relname regclass, relfilenode oid);');
+# Save the relfilenode of a set of toast indexes, one from the catalog
+# pg_constraint and one from the test table.  This data is used for checks
+# after some of the REINDEX operations done below, checking if they are
+# changed.
+my $fetch_toast_relfilenodes = qq{SELECT c.oid::regclass, c.relfilenode
+  FROM pg_class a
+    JOIN pg_class b ON (a.oid = b.reltoastrelid)
+    JOIN pg_index i on (a.oid = i.indrelid)
+    JOIN pg_class c on (i.indexrelid = c.oid)
+  WHERE b.oid IN ('pg_constraint'::regclass, 'test1'::regclass)};
+# Same for relfilenodes of normal indexes.  This saves the relfilenode
+# from a catalog of pg_constraint, and the one from the test table.
+my $fetch_index_relfilenodes = qq{SELECT oid, relfilenode
+  FROM pg_class
+  WHERE relname IN ('pg_constraint_oid_index', 'test1x')};
+my $save_relfilenodes =
+	"INSERT INTO toast_relfilenodes $fetch_toast_relfilenodes;"
+  . "INSERT INTO toast_relfilenodes $fetch_index_relfilenodes;";
+
+# Query to compare a set of relfilenodes saved with the contents of pg_class.
+# Note that this does not join using OIDs, as CONCURRENTLY would change them
+# when reindexing.  A filter is applied on the toast index names, even if this
+# does not make a difference between the catalog and normal ones, the ordering
+# based on the name is enough to ensure a fixed output.
+my $compare_relfilenodes =
+  qq(SELECT regexp_replace(b.relname::text, '(pg_toast.pg_toast_)\\d{4,5}(_index)', '\\1<oid>\\2'),
+  CASE WHEN a.relfilenode = b.relfilenode THEN 'relfilenode is unchanged'
+    ELSE 'relfilenode has changed' END
+  FROM toast_relfilenodes b
+    JOIN pg_class a ON b.relname::text = a.oid::regclass::text
+  ORDER BY b.relname::text);
+
+# Save the set of relfilenodes and compare them.
+$node->safe_psql('postgres', $save_relfilenodes);
+$node->issues_sql_like(
+	[ 'reindexdb', 'postgres' ],
+	qr/statement: REINDEX DATABASE postgres;/,
+	'SQL REINDEX run');
+my $relnode_info = $node->safe_psql('postgres', $compare_relfilenodes);
+is( $relnode_info, qq(pg_constraint_oid_index|relfilenode is unchanged
+pg_toast.pg_toast_<oid>_index|relfilenode has changed
+pg_toast.pg_toast_<oid>_index|relfilenode is unchanged
+test1x|relfilenode has changed), 'relfilenode change after REINDEX DATABASE');
+
+# Re-save and run the second one.
+$node->safe_psql('postgres',
+	"TRUNCATE toast_relfilenodes; $save_relfilenodes");
+$node->issues_sql_like(
+	[ 'reindexdb', '-s', 'postgres' ],
+	qr/statement: REINDEX SYSTEM postgres;/,
+	'reindex system tables');
+$relnode_info = $node->safe_psql('postgres', $compare_relfilenodes);
+is( $relnode_info, qq(pg_constraint_oid_index|relfilenode has changed
+pg_toast.pg_toast_<oid>_index|relfilenode is unchanged
+pg_toast.pg_toast_<oid>_index|relfilenode has changed
+test1x|relfilenode is unchanged), 'relfilenode change after REINDEX SYSTEM');
 
 $node->issues_sql_like(
 	[ 'reindexdb', '-t', 'test1', 'postgres' ],
@@ -57,10 +117,6 @@ $node->issues_sql_like(
 	[ 'reindexdb', '-S', 'pg_catalog', 'postgres' ],
 	qr/statement: REINDEX SCHEMA pg_catalog;/,
 	'reindex specific schema');
-$node->issues_sql_like(
-	[ 'reindexdb', '-s', 'postgres' ],
-	qr/statement: REINDEX SYSTEM postgres;/,
-	'reindex system tables');
 $node->issues_sql_like(
 	[ 'reindexdb', '-v', '-t', 'test1', 'postgres' ],
 	qr/statement: REINDEX \(VERBOSE\) TABLE public\.test1;/,
@@ -176,11 +232,6 @@ $node->command_fails(
 $node->command_fails(
 	[ 'reindexdb', '-j', '2', '-i', 'i1', 'postgres' ],
 	'parallel reindexdb cannot process indexes');
-$node->issues_sql_like(
-	[ 'reindexdb', '-j', '2', 'postgres' ],
-	qr/statement:\ REINDEX SYSTEM postgres;
-.*statement:\ REINDEX TABLE public\.test1/s,
-	'parallel reindexdb for database issues REINDEX SYSTEM first');
 # Note that the ordering of the commands is not stable, so the second
 # command for s2.t2 is not checked after.
 $node->issues_sql_like(
@@ -190,13 +241,8 @@ $node->issues_sql_like(
 $node->command_ok(
 	[ 'reindexdb', '-j', '2', '-S', 's3' ],
 	'parallel reindexdb with empty schema');
-$node->command_checks_all(
+$node->command_ok(
 	[ 'reindexdb', '-j', '2', '--concurrently', '-d', 'postgres' ],
-	0,
-	[qr/^$/],
-	[
-		qr/^reindexdb: warning: cannot reindex system catalogs concurrently, skipping all/s
-	],
-	'parallel reindexdb for system with --concurrently skips catalogs');
+	'parallel reindexdb with empty schema, concurrently');
 
 done_testing();
diff --git a/doc/src/sgml/ref/reindex.sgml b/doc/src/sgml/ref/reindex.sgml
index 336ca24b31..fcbda88149 100644
--- a/doc/src/sgml/ref/reindex.sgml
+++ b/doc/src/sgml/ref/reindex.sgml
@@ -21,7 +21,8 @@ PostgreSQL documentation
 
  <refsynopsisdiv>
 <synopsis>
-REINDEX [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] { INDEX | TABLE | SCHEMA | DATABASE | SYSTEM } [ CONCURRENTLY ] <replaceable class="parameter">name</replaceable>
+REINDEX [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] { INDEX | TABLE | SCHEMA } [ CONCURRENTLY ] <replaceable class="parameter">name</replaceable>
+REINDEX [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] { DATABASE | SYSTEM } [ CONCURRENTLY ] [ <replaceable class="parameter">name</replaceable> ]
 
 <phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
 
@@ -126,8 +127,9 @@ REINDEX [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] { IN
     <term><literal>DATABASE</literal></term>
     <listitem>
      <para>
-      Recreate all indexes within the current database.
-      Indexes on shared system catalogs are also processed.
+      Recreate all indexes within the current database, except system
+      catalogs.
+      Indexes on shared system catalogs are not processed.
       This form of <command>REINDEX</command> cannot be executed inside a
       transaction block.
      </para>
@@ -154,8 +156,8 @@ REINDEX [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] { IN
       The name of the specific index, table, or database to be
       reindexed.  Index and table names can be schema-qualified.
       Presently, <command>REINDEX DATABASE</command> and <command>REINDEX SYSTEM</command>
-      can only reindex the current database, so their parameter must match
-      the current database's name.
+      can only reindex the current database. Their parameter is optional,
+      and it must match the current database's name.
      </para>
     </listitem>
    </varlistentry>
-- 
2.36.1

Attachment: signature.asc
Description: PGP signature

Reply via email to