Hello,

Now that we have the infrastructure to track indexes that might be corrupted
due to changes in collation libraries, I think it would be a good idea to offer
an easy way for users to reindex all indexes that might be corrupted.

I'm attaching a POC patch as a discussion basis.  It implements a new
"COLLATION" option to reindex, with "not_current" being the only accepted
value.  Note that I didn't spent too much efforts on the grammar part yet.

So for instance you can do:

REINDEX (COLLATION 'not_current') DATABASE mydb;

The filter is also implemented so that you could cumulate multiple filters, so
it could be easy to add more filtering, for instance:

REINDEX (COLLATION 'libc', COLLATION 'not_current') DATABASE mydb;

to only rebuild indexes depending on outdated libc collations, or

REINDEX (COLLATION 'libc', VERSION 'X.Y') DATABASE mydb;

to only rebuild indexes depending on a specific version of libc.
>From 5acf42e15c0dc8b185547ff9cb9371a86a057ec9 Mon Sep 17 00:00:00 2001
From: Julien Rouhaud <julien.rouh...@free.fr>
Date: Thu, 3 Dec 2020 15:54:42 +0800
Subject: [PATCH v1] Add a new COLLATION option to REINDEX.

---
 doc/src/sgml/ref/reindex.sgml              | 13 +++++
 src/backend/catalog/index.c                | 59 +++++++++++++++++++++-
 src/backend/commands/indexcmds.c           | 12 +++--
 src/backend/utils/cache/relcache.c         | 43 ++++++++++++++++
 src/include/catalog/index.h                |  6 ++-
 src/include/utils/relcache.h               |  1 +
 src/test/regress/expected/create_index.out | 10 ++++
 src/test/regress/sql/create_index.sql      | 10 ++++
 8 files changed, 149 insertions(+), 5 deletions(-)

diff --git a/doc/src/sgml/ref/reindex.sgml b/doc/src/sgml/ref/reindex.sgml
index 6e1cf06713..eb8da9c070 100644
--- a/doc/src/sgml/ref/reindex.sgml
+++ b/doc/src/sgml/ref/reindex.sgml
@@ -25,6 +25,7 @@ REINDEX [ ( <replaceable 
class="parameter">option</replaceable> [, ...] ) ] { IN
 
 <phrase>where <replaceable class="parameter">option</replaceable> can be one 
of:</phrase>
 
+    COLLATION [ <replaceable class="parameter">text</replaceable> ]
     CONCURRENTLY [ <replaceable class="parameter">boolean</replaceable> ]
     VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
 </synopsis>
@@ -168,6 +169,18 @@ REINDEX [ ( <replaceable 
class="parameter">option</replaceable> [, ...] ) ] { IN
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>COLLATION</literal></term>
+    <listitem>
+     <para>
+      This option can be used to filter the list of indexes to rebuild.  The
+      only allowed value is <literal>'not_current'</literal>, which will only
+      process indexes that depend on a collation version different than the
+      current one.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>CONCURRENTLY</literal></term>
     <listitem>
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 731610c701..7d941f40af 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -99,6 +99,12 @@ typedef struct
        Oid                     pendingReindexedIndexes[FLEXIBLE_ARRAY_MEMBER];
 } SerializedReindexState;
 
+typedef struct
+{
+       Oid relid;      /* targetr index oid */
+       bool deprecated;        /* depends on at least on deprected collation? 
*/
+} IndexHasDeprecatedColl;
+
 /* non-export function prototypes */
 static bool relationHasPrimaryKey(Relation rel);
 static TupleDesc ConstructTupleDescriptor(Relation heapRelation,
@@ -1349,6 +1355,57 @@ index_check_collation_versions(Oid relid)
        list_free(context.warned_colls);
 }
 
+/*
+ * Detect if an index depends on at least one deprecated collation.
+ * This is a callback for visitDependenciesOf().
+ */
+static bool
+do_check_index_has_deprecated_collation(const ObjectAddress *otherObject,
+                                                                               
const char *version,
+                                                                               
char **new_version,
+                                                                               
void *data)
+{
+       IndexHasDeprecatedColl *context = data;
+       char *current_version;
+
+       /* We only care about dependencies on collations. */
+       if (otherObject->classId != CollationRelationId)
+               return false;
+
+       /* Fast exit if we already found a deprecated collation version. */
+       if (context->deprecated)
+               return false;
+
+       /* Ask the provider for the current version.  Give up if unsupported. */
+       current_version = get_collation_version_for_oid(otherObject->objectId);
+       if (!current_version)
+               return false;
+
+       if (!version || strcmp(version, current_version) != 0)
+               context->deprecated = true;
+
+       return false;
+}
+
+bool
+index_has_deprecated_collation(Oid relid)
+{
+       ObjectAddress object;
+       IndexHasDeprecatedColl context;
+
+       object.classId = RelationRelationId;
+       object.objectId = relid;
+       object.objectSubId = 0;
+
+       context.relid = relid;
+       context.deprecated = false;
+
+       visitDependenciesOf(&object, &do_check_index_has_deprecated_collation,
+                                               &context);
+
+       return context.deprecated;
+}
+
 /*
  * Update the version for collations.  A callback for visitDependenciesOf().
  */
@@ -3886,7 +3943,7 @@ reindex_relation(Oid relid, int flags, int options)
         * relcache to get this with a sequential scan if ignoring system
         * indexes.)
         */
-       indexIds = RelationGetIndexList(rel);
+       indexIds = RelationGetIndexListFiltered(rel, options);
 
        if (flags & REINDEX_REL_SUPPRESS_INDEX_USE)
        {
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 14d24b3cc4..2477ad6c74 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2462,6 +2462,7 @@ ReindexParseOptions(ParseState *pstate, ReindexStmt *stmt)
        int                     options = 0;
        bool            concurrently = false;
        bool            verbose = false;
+       bool            coll_filter = false;
 
        /* Parse option list */
        foreach(lc, stmt->params)
@@ -2472,6 +2473,9 @@ ReindexParseOptions(ParseState *pstate, ReindexStmt *stmt)
                        verbose = defGetBoolean(opt);
                else if (strcmp(opt->defname, "concurrently") == 0)
                        concurrently = defGetBoolean(opt);
+               else if ((strcmp(opt->defname, "collation") == 0)
+                                && (strcmp(defGetString(opt), "not_current") 
== 0))
+                       coll_filter = true;
                else
                        ereport(ERROR,
                                        (errcode(ERRCODE_SYNTAX_ERROR),
@@ -2482,7 +2486,8 @@ ReindexParseOptions(ParseState *pstate, ReindexStmt *stmt)
 
        options =
                (verbose ? REINDEXOPT_VERBOSE : 0) |
-               (concurrently ? REINDEXOPT_CONCURRENTLY : 0);
+               (concurrently ? REINDEXOPT_CONCURRENTLY : 0) |
+               (coll_filter ? REINDEXOPT_COLL_NOT_CURRENT : 0);
 
        return options;
 }
@@ -3150,7 +3155,7 @@ ReindexRelationConcurrently(Oid relationOid, int options)
                                                                                
          ShareUpdateExclusiveLock);
 
                                /* Add all the valid indexes of relation to 
list */
-                               foreach(lc, RelationGetIndexList(heapRelation))
+                               foreach(lc, 
RelationGetIndexListFiltered(heapRelation, options))
                                {
                                        Oid                     cellOid = 
lfirst_oid(lc);
                                        Relation        indexRelation = 
index_open(cellOid,
@@ -3196,7 +3201,8 @@ ReindexRelationConcurrently(Oid relationOid, int options)
 
                                        MemoryContextSwitchTo(oldcontext);
 
-                                       foreach(lc2, 
RelationGetIndexList(toastRelation))
+                                       foreach(lc2, 
RelationGetIndexListFiltered(toastRelation,
+                                                                               
                                          options))
                                        {
                                                Oid                     cellOid 
= lfirst_oid(lc2);
                                                Relation        indexRelation = 
index_open(cellOid,
diff --git a/src/backend/utils/cache/relcache.c 
b/src/backend/utils/cache/relcache.c
index 66393becfb..9045dce2d7 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4609,6 +4609,49 @@ RelationGetIndexList(Relation relation)
        return result;
 }
 
+/*
+ * RelationGetIndexListFiltered -- get a filtered list of indexes on this
+ * relation.
+ *
+ * Calls RelationGetIndexList and filters out the result as required by the
+ * caller.  The filters are cumulative, although for now the only supported
+ * filter is for indexes that don't depend on deprecated collation version.
+ */
+List *
+RelationGetIndexListFiltered(Relation relation, int options)
+{
+       List       *result,
+                          *full_list;
+       ListCell   *lc;
+
+       full_list = RelationGetIndexList(relation);
+
+       /* Fast exit if no filtering was asked, or if the list if empty. */
+       if (!reindexHasFilter(options) || full_list == NIL)
+               return full_list;
+
+       result = NIL;
+       foreach(lc, full_list)
+       {
+               Oid             indexOid = lfirst_oid(lc);
+
+               /*
+                * The caller wants to discard indexes that don't depend on a
+                * deprecated collation version.
+                */
+               if ((options & REINDEXOPT_COLL_NOT_CURRENT) != 0)
+               {
+                       if (!index_has_deprecated_collation(indexOid))
+                               continue;
+               }
+
+               /* Index passed all filters, keep it */
+               result = lappend_oid(result, indexOid);
+       }
+
+       return result;
+}
+
 /*
  * RelationGetStatExtList
  *             get a list of OIDs of statistics objects on this relation
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index c041628049..9f2bb60343 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -35,9 +35,12 @@ typedef enum ReindexOption
        REINDEXOPT_VERBOSE = 1 << 0,    /* print progress info */
        REINDEXOPT_REPORT_PROGRESS = 1 << 1,    /* report pgstat progress */
        REINDEXOPT_MISSING_OK = 1 << 2, /* skip missing relations */
-       REINDEXOPT_CONCURRENTLY = 1 << 3        /* concurrent mode */
+       REINDEXOPT_CONCURRENTLY = 1 << 3,       /* concurrent mode */
+       REINDEXOPT_COLL_NOT_CURRENT = 1 << 4
 } ReindexOption;
 
+#define reindexHasFilter(x)            ((x & REINDEXOPT_COLL_NOT_CURRENT) != 0)
+
 /* state info for validate_index bulkdelete callback */
 typedef struct ValidateIndexState
 {
@@ -131,6 +134,7 @@ extern void FormIndexDatum(IndexInfo *indexInfo,
                                                   bool *isnull);
 
 extern void index_check_collation_versions(Oid relid);
+extern bool index_has_deprecated_collation(Oid relid);
 extern void index_update_collation_versions(Oid relid, Oid coll);
 
 extern void index_build(Relation heapRelation,
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 9a85b7dd57..4ef02c9851 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -45,6 +45,7 @@ extern void RelationClose(Relation relation);
  */
 extern List *RelationGetFKeyList(Relation relation);
 extern List *RelationGetIndexList(Relation relation);
+extern List *RelationGetIndexListFiltered(Relation relation, int options);
 extern List *RelationGetStatExtList(Relation relation);
 extern Oid     RelationGetPrimaryKeyIndex(Relation relation);
 extern Oid     RelationGetReplicaIndex(Relation relation);
diff --git a/src/test/regress/expected/create_index.out 
b/src/test/regress/expected/create_index.out
index fc6afab58a..39a5b91d1a 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -2018,6 +2018,16 @@ INFO:  index "reindex_verbose_pkey" was reindexed
 \set VERBOSITY default
 DROP TABLE reindex_verbose;
 --
+-- REINDEX (COLLATION 'not_current')
+--
+CREATE TABLE reindex_coll(id integer primary key);
+\set VERBOSITY terse \\ -- suppress machine-dependent details
+-- no suitable index should be found
+REINDEX (COLLATION 'not_current') TABLE reindex_coll;
+NOTICE:  table "reindex_coll" has no indexes to reindex
+\set VERBOSITY default
+DROP TABLE reindex_coll;
+--
 -- REINDEX CONCURRENTLY
 --
 CREATE TABLE concur_reindex_tab (c1 int);
diff --git a/src/test/regress/sql/create_index.sql 
b/src/test/regress/sql/create_index.sql
index 824cb9f9e8..99f45c0d02 100644
--- a/src/test/regress/sql/create_index.sql
+++ b/src/test/regress/sql/create_index.sql
@@ -790,6 +790,16 @@ REINDEX (VERBOSE) TABLE reindex_verbose;
 \set VERBOSITY default
 DROP TABLE reindex_verbose;
 
+--
+-- REINDEX (COLLATION 'not_current')
+--
+CREATE TABLE reindex_coll(id integer primary key);
+\set VERBOSITY terse \\ -- suppress machine-dependent details
+-- no suitable index should be found
+REINDEX (COLLATION 'not_current') TABLE reindex_coll;
+\set VERBOSITY default
+DROP TABLE reindex_coll;
+
 --
 -- REINDEX CONCURRENTLY
 --
-- 
2.20.1

Reply via email to