Track collation versions for indexes.
authorThomas Munro <[email protected]>
Mon, 2 Nov 2020 06:50:45 +0000 (19:50 +1300)
committerThomas Munro <[email protected]>
Mon, 2 Nov 2020 12:19:50 +0000 (01:19 +1300)
Record the current version of dependent collations in pg_depend when
creating or rebuilding an index.  When accessing the index later, warn
that the index may be corrupted if the current version doesn't match.

Thanks to Douglas Doole, Peter Eisentraut, Christoph Berg, Laurenz Albe,
Michael Paquier, Robert Haas, Tom Lane and others for very helpful
discussion.

Author: Thomas Munro <[email protected]>
Author: Julien Rouhaud <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]> (earlier versions)
Discussion: https://postgr.es/m/CAEepm%3D0uEQCpfq_%2BLYFBdArCe4Ot98t1aR4eYiYTe%3DyavQygiQ%40mail.gmail.com

43 files changed:
doc/src/sgml/catalogs.sgml
doc/src/sgml/charset.sgml
doc/src/sgml/func.sgml
doc/src/sgml/ref/alter_index.sgml
doc/src/sgml/ref/pgupgrade.sgml
doc/src/sgml/ref/reindex.sgml
src/backend/catalog/dependency.c
src/backend/catalog/heap.c
src/backend/catalog/index.c
src/backend/catalog/pg_constraint.c
src/backend/catalog/pg_depend.c
src/backend/catalog/pg_type.c
src/backend/commands/collationcmds.c
src/backend/commands/tablecmds.c
src/backend/nodes/copyfuncs.c
src/backend/optimizer/util/plancat.c
src/backend/parser/gram.y
src/backend/utils/adt/pg_locale.c
src/backend/utils/adt/pg_upgrade_support.c
src/backend/utils/cache/relcache.c
src/bin/pg_dump/pg_backup.h
src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/pg_dump.h
src/bin/pg_upgrade/dump.c
src/bin/pg_upgrade/option.c
src/bin/pg_upgrade/pg_upgrade.h
src/bin/psql/tab-complete.c
src/include/catalog/catversion.h
src/include/catalog/dependency.h
src/include/catalog/index.h
src/include/catalog/pg_depend.h
src/include/catalog/pg_type.h
src/include/nodes/parsenodes.h
src/include/utils/pg_locale.h
src/include/utils/rel.h
src/test/Makefile
src/test/locale/.gitignore
src/test/locale/Makefile
src/test/locale/t/001_index.pl [new file with mode: 0644]
src/test/regress/expected/collate.icu.utf8.out
src/test/regress/expected/create_index.out
src/test/regress/sql/collate.icu.utf8.sql
src/tools/pgindent/typedefs.list

index c3f324f05eb4c9e40ca8438099e5cab15215ec42..5fb9dca4258cb35b88e89dc6311f33ff0c8e42ad 100644 (file)
@@ -3308,7 +3308,8 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
        <structfield>refobjversion</structfield> <type>text</type>
        </para>
        <para>
-        An optional version for the referenced object.
+        An optional version for the referenced object.  Currently used for
+        indexes' collations (see <xref linkend="collation-versions"/>).
        </para>
       </entry>
      </row>
index 2745b4441765485858e26899afa666582c4d431d..832a701523de314f6e38756635e401e5c80a9b54 100644 (file)
@@ -948,6 +948,44 @@ CREATE COLLATION ignore_accents (provider = icu, locale = 'und-u-ks-level1-kc-tr
     </tip>
    </sect3>
   </sect2>
+
+  <sect2 id="collation-versions">
+   <title>Collation Versions</title>
+
+   <para>
+    The sort order defined by a collation is not necessarily fixed over time.
+    <productname>PostgreSQL</productname> relies on external libraries that
+    are subject to operating system upgrades, and can also differ between
+    servers involved in binary replication and file-system-level migration.
+    Persistent data structures such as B-trees that depend on sort order might
+    be corrupted by any resulting change.
+    <productname>PostgreSQL</productname> defends against this by recording the
+    current version of each referenced collation for any index that depends on
+    it in the
+    <link linkend="catalog-pg-depend"><structname>pg_depend</structname></link>
+    catalog, if the collation provider makes that information available.  If the
+    provider later begins to report a different version, a warning will be
+    issued when the index is accessed, until either the
+    <xref linkend="sql-reindex"/> command or the
+    <xref linkend="sql-alterindex"/> command is used to update the version.
+   </para>
+   <para>
+    Version information is available from the
+    <literal>icu</literal> provider on all operating systems.  For the
+    <literal>libc</literal> provider, versions are currently only available
+    on systems using the GNU C library (most Linux systems) and Windows.
+   </para>
+
+   <note>
+    <para>
+     When using the GNU C library for collations, the C library's version
+     is used as a proxy for the collation version.  Many Linux distributions
+     change collation definitions only when upgrading the C library, but this
+     approach is imperfect as maintainers are free to back-port newer
+     collation definitions to older C library releases.
+    </para>
+   </note>
+  </sect2>
  </sect1>
 
  <sect1 id="multibyte">
index 0398b4909bf8d9dc0bbb2fcbb8cac3bd981c6133..bf6004f321f6350d5055f8922dda1040f6897426 100644 (file)
@@ -25444,7 +25444,9 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </para>
        <para>
         Returns the actual version of the collation object as it is currently
-        installed in the operating system.
+        installed in the operating system.  <literal>null</literal> is returned
+        on operating systems where <productname>PostgreSQL</productname>
+        doesn't have support for versions.
        </para></entry>
       </row>
 
index 793119d2fc1a5d95972523d28c63c9ca3315cfc6..214005a86c5fb6b4cb97e920e90b218119e0a71c 100644 (file)
@@ -25,6 +25,7 @@ ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> RENA
 ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> SET TABLESPACE <replaceable class="parameter">tablespace_name</replaceable>
 ALTER INDEX <replaceable class="parameter">name</replaceable> ATTACH PARTITION <replaceable class="parameter">index_name</replaceable>
 ALTER INDEX <replaceable class="parameter">name</replaceable> DEPENDS ON EXTENSION <replaceable class="parameter">extension_name</replaceable>
+ALTER INDEX <replaceable class="parameter">name</replaceable> ALTER COLLATION <replaceable class="parameter">collation_name</replaceable> REFRESH VERSION
 ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )
 ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> RESET ( <replaceable class="parameter">storage_parameter</replaceable> [, ... ] )
 ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> ALTER [ COLUMN ] <replaceable class="parameter">column_number</replaceable>
@@ -112,6 +113,20 @@ ALTER INDEX ALL IN TABLESPACE <replaceable class="parameter">name</replaceable>
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ALTER COLLATION <replaceable class="parameter">collation_name</replaceable> REFRESH VERSION</literal></term>
+    <listitem>
+     <para>
+      Silences warnings about mismatched collation versions, by declaring
+      that the index is compatible with the current collation definition.
+      Be aware that incorrect use of this command can hide index corruption.
+      If you don't know whether a collation's definition has changed
+      incompatibly, <xref linkend="sql-reindex"/> is a safe alternative.
+      See <xref linkend="collation-versions"/> for more information.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>SET ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] )</literal></term>
     <listitem>
index b59c5697a36ceae9624838d4b7f26cc043c56586..92e1d09a55caee6f32ddc3ba6161dde6dd5e9c43 100644 (file)
@@ -215,6 +215,21 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--index-collation-versions-unknown</option></term>
+      <listitem>
+       <para>
+        When upgrading indexes from releases before 14 that didn't track
+        collation versions, <application>pg_upgrade</application>
+        assumes by default that the upgraded indexes are compatible with the
+        currently installed versions of relevant collations (see
+        <xref linkend="collation-versions"/>).  Specify
+        <option>--index-collation-versions-unknown</option> to mark
+        them as needing to be rebuilt instead.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-?</option></term>
       <term><option>--help</option></term>
index fa43e3a97202321d105e57b1e563ea8581d319a8..f6d425a6910e0bf03d9f47968eae855f2c8c13dc 100644 (file)
@@ -38,6 +38,15 @@ REINDEX [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] { IN
    several scenarios in which to use <command>REINDEX</command>:
 
    <itemizedlist>
+    <listitem>
+     <para>
+      The index depends on the sort order of a collation, and the definition
+      of the collation has changed.  This can cause index scans to fail to
+      find keys that are present.  See <xref linkend="collation-versions"/> for
+      more information.
+     </para>
+    </listitem>
+
     <listitem>
      <para>
       An index has become corrupted, and no longer contains valid
index 1a927377e739d6c20d2e7b2fcdc9f63ce1fc1eb5..b0d037600e93b33cefd0b7236537fd684fe8350a 100644 (file)
@@ -76,6 +76,7 @@
 #include "rewrite/rewriteRemove.h"
 #include "storage/lmgr.h"
 #include "utils/acl.h"
+#include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
@@ -434,6 +435,84 @@ performMultipleDeletions(const ObjectAddresses *objects,
    table_close(depRel, RowExclusiveLock);
 }
 
+/*
+ * Call a function for all objects that 'object' depend on.  If the function
+ * returns true, refobjversion will be updated in the catalog.
+ */
+void
+visitDependenciesOf(const ObjectAddress *object,
+                   VisitDependenciesOfCB callback,
+                   void *userdata)
+{
+   Relation    depRel;
+   ScanKeyData key[3];
+   SysScanDesc scan;
+   HeapTuple   tup;
+   ObjectAddress otherObject;
+
+   ScanKeyInit(&key[0],
+               Anum_pg_depend_classid,
+               BTEqualStrategyNumber, F_OIDEQ,
+               ObjectIdGetDatum(object->classId));
+   ScanKeyInit(&key[1],
+               Anum_pg_depend_objid,
+               BTEqualStrategyNumber, F_OIDEQ,
+               ObjectIdGetDatum(object->objectId));
+   ScanKeyInit(&key[2],
+               Anum_pg_depend_objsubid,
+               BTEqualStrategyNumber, F_INT4EQ,
+               Int32GetDatum(object->objectSubId));
+
+   depRel = table_open(DependRelationId, RowExclusiveLock);
+   scan = systable_beginscan(depRel, DependDependerIndexId, true,
+                             NULL, 3, key);
+
+   while (HeapTupleIsValid(tup = systable_getnext(scan)))
+   {
+       Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
+       char       *new_version;
+       Datum       depversion;
+       bool        isnull;
+
+       otherObject.classId = foundDep->refclassid;
+       otherObject.objectId = foundDep->refobjid;
+       otherObject.objectSubId = foundDep->refobjsubid;
+
+       depversion = heap_getattr(tup, Anum_pg_depend_refobjversion,
+                                 RelationGetDescr(depRel), &isnull);
+
+       /* Does the callback want to update the version? */
+       if (callback(&otherObject,
+                    isnull ? NULL : TextDatumGetCString(depversion),
+                    &new_version,
+                    userdata))
+       {
+           Datum       values[Natts_pg_depend];
+           bool        nulls[Natts_pg_depend];
+           bool        replaces[Natts_pg_depend];
+
+           memset(values, 0, sizeof(values));
+           memset(nulls, false, sizeof(nulls));
+           memset(replaces, false, sizeof(replaces));
+
+           if (new_version)
+               values[Anum_pg_depend_refobjversion - 1] =
+                   CStringGetTextDatum(new_version);
+           else
+               nulls[Anum_pg_depend_refobjversion - 1] = true;
+           replaces[Anum_pg_depend_refobjversion - 1] = true;
+
+           tup = heap_modify_tuple(tup, RelationGetDescr(depRel), values,
+                                   nulls, replaces);
+           CatalogTupleUpdate(depRel, &tup->t_self, tup);
+
+           heap_freetuple(tup);
+       }
+   }
+   systable_endscan(scan);
+   table_close(depRel, RowExclusiveLock);
+}
+
 /*
  * findDependentObjects - find all objects that depend on 'object'
  *
@@ -1566,6 +1645,38 @@ ReleaseDeletionLock(const ObjectAddress *object)
                             AccessExclusiveLock);
 }
 
+/*
+ * Record dependencies on a list of collations, optionally with their current
+ * version.
+ */
+void
+recordDependencyOnCollations(ObjectAddress *myself,
+                            List *collations,
+                            bool record_version)
+{
+   ObjectAddresses *addrs;
+   ListCell   *lc;
+
+   if (list_length(collations) == 0)
+       return;
+
+   addrs = new_object_addresses();
+   foreach(lc, collations)
+   {
+       ObjectAddress referenced;
+
+       ObjectAddressSet(referenced, CollationRelationId, lfirst_oid(lc));
+
+       add_exact_object_address(&referenced, addrs);
+   }
+
+   eliminate_duplicate_dependencies(addrs);
+   recordMultipleDependencies(myself, addrs->refs, addrs->numrefs,
+                              DEPENDENCY_NORMAL, record_version);
+
+   free_object_addresses(addrs);
+}
+
 /*
  * recordDependencyOnExpr - find expression dependencies
  *
@@ -1602,8 +1713,8 @@ recordDependencyOnExpr(const ObjectAddress *depender,
    recordMultipleDependencies(depender,
                               context.addrs->refs,
                               context.addrs->numrefs,
-                              NULL,
-                              behavior);
+                              behavior,
+                              false);
 
    free_object_addresses(context.addrs);
 }
@@ -1630,7 +1741,8 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
                                Node *expr, Oid relId,
                                DependencyType behavior,
                                DependencyType self_behavior,
-                               bool reverse_self)
+                               bool reverse_self,
+                               bool record_version)
 {
    find_expr_references_context context;
    RangeTblEntry rte;
@@ -1691,8 +1803,8 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
            recordMultipleDependencies(depender,
                                       self_addrs->refs,
                                       self_addrs->numrefs,
-                                      NULL,
-                                      self_behavior);
+                                      self_behavior,
+                                      record_version);
        else
        {
            /* Can't use recordMultipleDependencies, so do it the hard way */
@@ -1713,8 +1825,8 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
    recordMultipleDependencies(depender,
                               context.addrs->refs,
                               context.addrs->numrefs,
-                              NULL,
-                              behavior);
+                              behavior,
+                              record_version);
 
    free_object_addresses(context.addrs);
 }
@@ -1770,6 +1882,29 @@ find_expr_references_walker(Node *node,
            /* If it's a plain relation, reference this column */
            add_object_address(OCLASS_CLASS, rte->relid, var->varattno,
                               context->addrs);
+
+           /* Top-level collation if valid */
+           if (OidIsValid(var->varcollid))
+               add_object_address(OCLASS_COLLATION, var->varcollid, 0,
+                                  context->addrs);
+           /* Otherwise, it may be a type with internal collations */
+           else if (var->vartype >= FirstNormalObjectId)
+           {
+               List       *collations;
+               ListCell   *lc;
+
+               collations = GetTypeCollations(var->vartype);
+
+               foreach(lc, collations)
+               {
+                   Oid         coll = lfirst_oid(lc);
+
+                   if (OidIsValid(coll))
+                       add_object_address(OCLASS_COLLATION,
+                                          lfirst_oid(lc), 0,
+                                          context->addrs);
+               }
+           }
        }
 
        /*
@@ -1794,11 +1929,9 @@ find_expr_references_walker(Node *node,
        /*
         * We must also depend on the constant's collation: it could be
         * different from the datatype's, if a CollateExpr was const-folded to
-        * a simple constant.  However we can save work in the most common
-        * case where the collation is "default", since we know that's pinned.
+        * a simple constant.
         */
-       if (OidIsValid(con->constcollid) &&
-           con->constcollid != DEFAULT_COLLATION_OID)
+       if (OidIsValid(con->constcollid))
            add_object_address(OCLASS_COLLATION, con->constcollid, 0,
                               context->addrs);
 
@@ -1887,8 +2020,7 @@ find_expr_references_walker(Node *node,
        add_object_address(OCLASS_TYPE, param->paramtype, 0,
                           context->addrs);
        /* and its collation, just as for Consts */
-       if (OidIsValid(param->paramcollid) &&
-           param->paramcollid != DEFAULT_COLLATION_OID)
+       if (OidIsValid(param->paramcollid))
            add_object_address(OCLASS_COLLATION, param->paramcollid, 0,
                               context->addrs);
    }
@@ -1975,8 +2107,7 @@ find_expr_references_walker(Node *node,
            add_object_address(OCLASS_TYPE, fselect->resulttype, 0,
                               context->addrs);
        /* the collation might not be referenced anywhere else, either */
-       if (OidIsValid(fselect->resultcollid) &&
-           fselect->resultcollid != DEFAULT_COLLATION_OID)
+       if (OidIsValid(fselect->resultcollid))
            add_object_address(OCLASS_COLLATION, fselect->resultcollid, 0,
                               context->addrs);
    }
@@ -2006,8 +2137,7 @@ find_expr_references_walker(Node *node,
        add_object_address(OCLASS_TYPE, relab->resulttype, 0,
                           context->addrs);
        /* the collation might not be referenced anywhere else, either */
-       if (OidIsValid(relab->resultcollid) &&
-           relab->resultcollid != DEFAULT_COLLATION_OID)
+       if (OidIsValid(relab->resultcollid))
            add_object_address(OCLASS_COLLATION, relab->resultcollid, 0,
                               context->addrs);
    }
@@ -2019,8 +2149,7 @@ find_expr_references_walker(Node *node,
        add_object_address(OCLASS_TYPE, iocoerce->resulttype, 0,
                           context->addrs);
        /* the collation might not be referenced anywhere else, either */
-       if (OidIsValid(iocoerce->resultcollid) &&
-           iocoerce->resultcollid != DEFAULT_COLLATION_OID)
+       if (OidIsValid(iocoerce->resultcollid))
            add_object_address(OCLASS_COLLATION, iocoerce->resultcollid, 0,
                               context->addrs);
    }
@@ -2032,8 +2161,7 @@ find_expr_references_walker(Node *node,
        add_object_address(OCLASS_TYPE, acoerce->resulttype, 0,
                           context->addrs);
        /* the collation might not be referenced anywhere else, either */
-       if (OidIsValid(acoerce->resultcollid) &&
-           acoerce->resultcollid != DEFAULT_COLLATION_OID)
+       if (OidIsValid(acoerce->resultcollid))
            add_object_address(OCLASS_COLLATION, acoerce->resultcollid, 0,
                               context->addrs);
        /* fall through to examine arguments */
@@ -2121,8 +2249,7 @@ find_expr_references_walker(Node *node,
        if (OidIsValid(wc->endInRangeFunc))
            add_object_address(OCLASS_PROC, wc->endInRangeFunc, 0,
                               context->addrs);
-       if (OidIsValid(wc->inRangeColl) &&
-           wc->inRangeColl != DEFAULT_COLLATION_OID)
+       if (OidIsValid(wc->inRangeColl))
            add_object_address(OCLASS_COLLATION, wc->inRangeColl, 0,
                               context->addrs);
        /* fall through to examine substructure */
@@ -2267,7 +2394,7 @@ find_expr_references_walker(Node *node,
        {
            Oid         collid = lfirst_oid(ct);
 
-           if (OidIsValid(collid) && collid != DEFAULT_COLLATION_OID)
+           if (OidIsValid(collid))
                add_object_address(OCLASS_COLLATION, collid, 0,
                                   context->addrs);
        }
@@ -2289,7 +2416,7 @@ find_expr_references_walker(Node *node,
        {
            Oid         collid = lfirst_oid(ct);
 
-           if (OidIsValid(collid) && collid != DEFAULT_COLLATION_OID)
+           if (OidIsValid(collid))
                add_object_address(OCLASS_COLLATION, collid, 0,
                                   context->addrs);
        }
@@ -2685,8 +2812,9 @@ record_object_address_dependencies(const ObjectAddress *depender,
 {
    eliminate_duplicate_dependencies(referenced);
    recordMultipleDependencies(depender,
-                              referenced->refs, referenced->numrefs, NULL,
-                              behavior);
+                              referenced->refs, referenced->numrefs,
+                              behavior,
+                              false);
 }
 
 /*
index 9ccf378d45b81dbee384e993a94c6436c6e44d82..4cd7d76938101f4cd897284ec98f4b57b2967f6a 100644 (file)
@@ -2336,7 +2336,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
         */
        recordDependencyOnSingleRelExpr(&colobject, expr, RelationGetRelid(rel),
                                        DEPENDENCY_AUTO,
-                                       DEPENDENCY_AUTO, false);
+                                       DEPENDENCY_AUTO, false, false);
    }
    else
    {
@@ -2346,7 +2346,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
         */
        recordDependencyOnSingleRelExpr(&defobject, expr, RelationGetRelid(rel),
                                        DEPENDENCY_NORMAL,
-                                       DEPENDENCY_NORMAL, false);
+                                       DEPENDENCY_NORMAL, false, false);
    }
 
    /*
@@ -3706,7 +3706,8 @@ StorePartitionKey(Relation rel,
                                        RelationGetRelid(rel),
                                        DEPENDENCY_NORMAL,
                                        DEPENDENCY_INTERNAL,
-                                       true /* reverse the self-deps */ );
+                                       true /* reverse the self-deps */ ,
+                                       false /* don't track versions */ );
 
    /*
     * We must invalidate the relcache so that the next
index a18cc7cad309f392bd46612c7f227697712b65c0..b88b4a1f12b6bbfedd8a75486d6956f9e23cd798 100644 (file)
@@ -53,6 +53,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "commands/defrem.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -75,6 +76,7 @@
 #include "utils/guc.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
+#include "utils/pg_locale.h"
 #include "utils/memutils.h"
 #include "utils/pg_rusage.h"
 #include "utils/rel.h"
@@ -1020,6 +1022,8 @@ index_create(Relation heapRelation,
        ObjectAddress myself,
                    referenced;
        ObjectAddresses *addrs;
+       List       *colls = NIL,
+                  *colls_no_version = NIL;
 
        ObjectAddressSet(myself, RelationRelationId, indexRelationId);
 
@@ -1103,30 +1107,65 @@ index_create(Relation heapRelation,
            recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_SEC);
        }
 
-       /* placeholder for normal dependencies */
-       addrs = new_object_addresses();
-
-       /* Store dependency on collations */
-
-       /* The default collation is pinned, so don't bother recording it */
+       /* Get dependencies on collations for all index keys. */
        for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
        {
-           if (OidIsValid(collationObjectId[i]) &&
-               collationObjectId[i] != DEFAULT_COLLATION_OID)
+           Oid         colloid = collationObjectId[i];
+
+           if (OidIsValid(colloid))
            {
-               ObjectAddressSet(referenced, CollationRelationId,
-                                collationObjectId[i]);
-               add_exact_object_address(&referenced, addrs);
+               Oid         opclass = classObjectId[i];
+
+               /*
+                * The *_pattern_ops opclasses are special: they explicitly do
+                * not depend on collation order so we can save some effort.
+                *
+                * XXX With more analysis, we could also skip version tracking
+                * for some cases like hash indexes with deterministic
+                * collations, because they will never need to order strings.
+                */
+               if (opclass == TEXT_BTREE_PATTERN_OPS_OID ||
+                   opclass == VARCHAR_BTREE_PATTERN_OPS_OID ||
+                   opclass == BPCHAR_BTREE_PATTERN_OPS_OID)
+                   colls_no_version = lappend_oid(colls_no_version, colloid);
+               else
+                   colls = lappend_oid(colls, colloid);
+           }
+           else
+           {
+               Form_pg_attribute att = TupleDescAttr(indexTupDesc, i);
+
+               Assert(i < indexTupDesc->natts);
+
+               /*
+                * Even though there is no top-level collation, there may be
+                * collations affecting ordering inside types, so look there
+                * too.
+                */
+               colls = list_concat(colls, GetTypeCollations(att->atttypid));
            }
        }
 
+       /*
+        * If we have anything in both lists, keep just the versioned one to
+        * avoid some duplication.
+        */
+       if (colls_no_version != NIL && colls != NIL)
+           colls_no_version = list_difference_oid(colls_no_version, colls);
+
+       /* Store the versioned and unversioned collation dependencies. */
+       if (colls_no_version != NIL)
+           recordDependencyOnCollations(&myself, colls_no_version, false);
+       if (colls != NIL)
+           recordDependencyOnCollations(&myself, colls, true);
+
        /* Store dependency on operator classes */
+       addrs = new_object_addresses();
        for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
        {
            ObjectAddressSet(referenced, OperatorClassRelationId, classObjectId[i]);
            add_exact_object_address(&referenced, addrs);
        }
-
        record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
        free_object_addresses(addrs);
 
@@ -1137,7 +1176,7 @@ index_create(Relation heapRelation,
                                            (Node *) indexInfo->ii_Expressions,
                                            heapRelationId,
                                            DEPENDENCY_NORMAL,
-                                           DEPENDENCY_AUTO, false);
+                                           DEPENDENCY_AUTO, false, true);
        }
 
        /* Store dependencies on anything mentioned in predicate */
@@ -1147,7 +1186,7 @@ index_create(Relation heapRelation,
                                            (Node *) indexInfo->ii_Predicate,
                                            heapRelationId,
                                            DEPENDENCY_NORMAL,
-                                           DEPENDENCY_AUTO, false);
+                                           DEPENDENCY_AUTO, false, true);
        }
    }
    else
@@ -1226,6 +1265,129 @@ index_create(Relation heapRelation,
    return indexRelationId;
 }
 
+typedef struct do_collation_version_check_context
+{
+   Oid         relid;
+   List       *warned_colls;
+} do_collation_version_check_context;
+
+/*
+ * Raise a warning if the recorded and current collation version don't match.
+ * This is a callback for visitDependenciesOf().
+ */
+static bool
+do_collation_version_check(const ObjectAddress *otherObject,
+                          const char *version,
+                          char **new_version,
+                          void *data)
+{
+   do_collation_version_check_context *context = data;
+   char       *current_version;
+
+   /* We only care about dependencies on collations with a version. */
+   if (!version || otherObject->classId != CollationRelationId)
+       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;
+
+   /*
+    * We don't expect too many duplicates, but it's possible, and we don't
+    * want to generate duplicate warnings.
+    */
+   if (list_member_oid(context->warned_colls, otherObject->objectId))
+       return false;
+
+   /* Do they match? */
+   if (strcmp(current_version, version) != 0)
+   {
+       /*
+        * The version has changed, probably due to an OS/library upgrade or
+        * streaming replication between different OS/library versions.
+        */
+       ereport(WARNING,
+               (errmsg("index \"%s\" depends on collation \"%s\" version \"%s\", but the current version is \"%s\"",
+                       get_rel_name(context->relid),
+                       get_collation_name(otherObject->objectId),
+                       version,
+                       current_version),
+                errdetail("The index may be corrupted due to changes in sort order."),
+                errhint("REINDEX to avoid the risk of corruption.")));
+
+       /* Remember not to complain about this collation again. */
+       context->warned_colls = lappend_oid(context->warned_colls,
+                                           otherObject->objectId);
+   }
+
+   return false;
+}
+
+/* index_check_collation_versions
+ *     Check the collation version for all dependencies on the given index.
+ */
+void
+index_check_collation_versions(Oid relid)
+{
+   do_collation_version_check_context context;
+   ObjectAddress object;
+
+   /*
+    * The callback needs the relid for error messages, and some scratch space
+    * to avoid duplicate warnings.
+    */
+   context.relid = relid;
+   context.warned_colls = NIL;
+
+   object.classId = RelationRelationId;
+   object.objectId = relid;
+   object.objectSubId = 0;
+
+   visitDependenciesOf(&object, &do_collation_version_check, &context);
+
+   list_free(context.warned_colls);
+}
+
+/*
+ * Update the version for collations.  A callback for visitDependenciesOf().
+ */
+static bool
+do_collation_version_update(const ObjectAddress *otherObject,
+                           const char *version,
+                           char **new_version,
+                           void *data)
+{
+   Oid        *coll = data;
+
+   /* We only care about dependencies on collations with versions. */
+   if (!version || otherObject->classId != CollationRelationId)
+       return false;
+
+   /* If we're only trying to update one collation, skip others. */
+   if (OidIsValid(*coll) && otherObject->objectId != *coll)
+       return false;
+
+   *new_version = get_collation_version_for_oid(otherObject->objectId);
+
+   return true;
+}
+
+/*
+ * Record the current versions of one or all collations that an index depends
+ * on.  InvalidOid means all.
+ */
+void
+index_update_collation_versions(Oid relid, Oid coll)
+{
+   ObjectAddress object;
+
+   object.classId = RelationRelationId;
+   object.objectId = relid;
+   object.objectSubId = 0;
+   visitDependenciesOf(&object, &do_collation_version_update, &coll);
+}
+
 /*
  * index_concurrently_create_copy
  *
@@ -1686,6 +1848,10 @@ index_concurrently_swap(Oid newIndexId, Oid oldIndexId, const char *oldName)
    changeDependenciesOf(RelationRelationId, oldIndexId, newIndexId);
    changeDependenciesOn(RelationRelationId, oldIndexId, newIndexId);
 
+   /* Now we have the old index's collation versions, so fix that. */
+   CommandCounterIncrement();
+   index_update_collation_versions(newIndexId, InvalidOid);
+
    /*
     * Copy over statistics from old to new index
     */
@@ -3638,6 +3804,9 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
    /* Close rels, but keep locks */
    index_close(iRel, NoLock);
    table_close(heapRelation, NoLock);
+
+   /* Record the current versions of all depended-on collations. */
+   index_update_collation_versions(indexId, InvalidOid);
 }
 
 /*
index 0d70cb0c3c983a14186b3582ab027c4a56f48852..93774c9d21a59927542eeaf20704b5e75ab08ea1 100644 (file)
@@ -362,7 +362,7 @@ CreateConstraintEntry(const char *constraintName,
         */
        recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
                                        DEPENDENCY_NORMAL,
-                                       DEPENDENCY_NORMAL, false);
+                                       DEPENDENCY_NORMAL, false, true);
    }
 
    /* Post creation hook for new constraint */
index 09c30b13e884386340b36acb5a8ee351d0d7075c..25290c821fda3a594b5a9756d557f981497753a2 100644 (file)
@@ -19,6 +19,7 @@
 #include "access/table.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
+#include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_depend.h"
 #include "catalog/pg_extension.h"
@@ -27,6 +28,7 @@
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
+#include "utils/pg_locale.h"
 #include "utils/rel.h"
 
 
@@ -45,19 +47,24 @@ recordDependencyOn(const ObjectAddress *depender,
                   const ObjectAddress *referenced,
                   DependencyType behavior)
 {
-   recordMultipleDependencies(depender, referenced, 1, NULL, behavior);
+   recordMultipleDependencies(depender, referenced, 1, behavior, false);
 }
 
 /*
  * Record multiple dependencies (of the same kind) for a single dependent
  * object.  This has a little less overhead than recording each separately.
+ *
+ * If record_version is true, then a record is added even if the referenced
+ * object is pinned, and the dependency version will be retrieved according to
+ * the referenced object kind.  For now, only collation version is
+ * supported.
  */
 void
 recordMultipleDependencies(const ObjectAddress *depender,
                           const ObjectAddress *referenced,
                           int nreferenced,
-                          const char *version,
-                          DependencyType behavior)
+                          DependencyType behavior,
+                          bool record_version)
 {
    Relation    dependDesc;
    CatalogIndexState indstate;
@@ -66,6 +73,7 @@ recordMultipleDependencies(const ObjectAddress *depender,
                max_slots,
                slot_init_count,
                slot_stored_count;
+   char       *version = NULL;
 
    if (nreferenced <= 0)
        return;                 /* nothing to do */
@@ -96,12 +104,38 @@ recordMultipleDependencies(const ObjectAddress *depender,
    slot_init_count = 0;
    for (i = 0; i < nreferenced; i++, referenced++)
    {
+       bool        ignore_systempin = false;
+
+       if (record_version)
+       {
+           /* For now we only know how to deal with collations. */
+           if (referenced->classId == CollationRelationId)
+           {
+               /* C and POSIX don't need version tracking. */
+               if (referenced->objectId == C_COLLATION_OID ||
+                   referenced->objectId == POSIX_COLLATION_OID)
+                   continue;
+
+               version = get_collation_version_for_oid(referenced->objectId);
+
+               /*
+                * Default collation is pinned, so we need to force recording
+                * the dependency to store the version.
+                */
+               if (referenced->objectId == DEFAULT_COLLATION_OID)
+                   ignore_systempin = true;
+           }
+       }
+       else
+           Assert(!version);
+
        /*
         * If the referenced object is pinned by the system, there's no real
-        * need to record dependencies on it.  This saves lots of space in
-        * pg_depend, so it's worth the time taken to check.
+        * need to record dependencies on it, unless we need to record a
+        * version.  This saves lots of space in pg_depend, so it's worth the
+        * time taken to check.
         */
-       if (isObjectPinned(referenced, dependDesc))
+       if (!ignore_systempin && isObjectPinned(referenced, dependDesc))
            continue;
 
        if (slot_init_count < max_slots)
index 0b04dff7731c10ebe5444fec31b21cf120f8e306..44eed1a0b34a57447b04b7627249a0f77ae1cc28 100644 (file)
@@ -15,6 +15,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/relation.h"
 #include "access/table.h"
 #include "access/xact.h"
 #include "catalog/binary_upgrade.h"
@@ -512,6 +513,65 @@ TypeCreate(Oid newTypeOid,
    return address;
 }
 
+/*
+ * Get a list of all distinct collations that the given type depends on.
+ */
+List *
+GetTypeCollations(Oid typeoid)
+{
+   List       *result = NIL;
+   HeapTuple   tuple;
+   Form_pg_type typeTup;
+
+   tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typeoid));
+   if (!HeapTupleIsValid(tuple))
+       elog(ERROR, "cache lookup failed for type %u", typeoid);
+   typeTup = (Form_pg_type) GETSTRUCT(tuple);
+
+   if (OidIsValid(typeTup->typcollation))
+       result = list_append_unique_oid(result, typeTup->typcollation);
+   else if (typeTup->typtype == TYPTYPE_COMPOSITE)
+   {
+       Relation    rel = relation_open(typeTup->typrelid, AccessShareLock);
+       TupleDesc   desc = RelationGetDescr(rel);
+
+       for (int i = 0; i < RelationGetNumberOfAttributes(rel); i++)
+       {
+           Form_pg_attribute att = TupleDescAttr(desc, i);
+
+           if (OidIsValid(att->attcollation))
+               result = list_append_unique_oid(result, att->attcollation);
+           else
+               result = list_concat_unique_oid(result,
+                                               GetTypeCollations(att->atttypid));
+       }
+
+       relation_close(rel, NoLock);
+   }
+   else if (typeTup->typtype == TYPTYPE_DOMAIN)
+   {
+       Assert(OidIsValid(typeTup->typbasetype));
+
+       result = list_concat_unique_oid(result,
+                                       GetTypeCollations(typeTup->typbasetype));
+   }
+   else if (typeTup->typtype == TYPTYPE_RANGE)
+   {
+       Oid         rangeid = get_range_subtype(typeTup->oid);
+
+       Assert(OidIsValid(rangeid));
+
+       result = list_concat_unique_oid(result, GetTypeCollations(rangeid));
+   }
+   else if (OidIsValid(typeTup->typelem))
+       result = list_concat_unique_oid(result,
+                                       GetTypeCollations(typeTup->typelem));
+
+   ReleaseSysCache(tuple);
+
+   return result;
+}
+
 /*
  * GenerateTypeDependencies: build the dependencies needed for a type
  *
index 5ad8886e60a44d78b1fbb2f22495f6aa9720ca37..7b31272734d06133f7170774c1bed5f8f31c8f11 100644 (file)
@@ -270,23 +270,9 @@ Datum
 pg_collation_actual_version(PG_FUNCTION_ARGS)
 {
    Oid         collid = PG_GETARG_OID(0);
-   HeapTuple   tp;
-   char       *collcollate;
-   char        collprovider;
    char       *version;
 
-   tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(collid));
-   if (!HeapTupleIsValid(tp))
-       ereport(ERROR,
-               (errcode(ERRCODE_UNDEFINED_OBJECT),
-                errmsg("collation with OID %u does not exist", collid)));
-
-   collcollate = pstrdup(NameStr(((Form_pg_collation) GETSTRUCT(tp))->collcollate));
-   collprovider = ((Form_pg_collation) GETSTRUCT(tp))->collprovider;
-
-   ReleaseSysCache(tp);
-
-   version = get_collation_actual_version(collprovider, collcollate);
+   version = get_collation_version_for_oid(collid);
 
    if (version)
        PG_RETURN_TEXT_P(cstring_to_text(version));
index df13b72974ffaba58807c3fe2a4d6f95f76ef02d..1b105ba1c428964ebad2df7b7d7d57c15663d92e 100644 (file)
@@ -93,6 +93,7 @@
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/partcache.h"
+#include "utils/pg_locale.h"
 #include "utils/relcache.h"
 #include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
@@ -559,6 +560,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
                                  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static void ATExecAlterCollationRefreshVersion(Relation rel, List *coll);
 
 
 /* ----------------------------------------------------------------
@@ -3986,6 +3988,10 @@ AlterTableGetLockLevel(List *cmds)
                cmd_lockmode = AccessShareLock;
                break;
 
+           case AT_AlterCollationRefreshVersion:
+               cmd_lockmode = AccessExclusiveLock;
+               break;
+
            default:            /* oops */
                elog(ERROR, "unrecognized alter table type: %d",
                     (int) cmd->subtype);
@@ -4160,6 +4166,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
            /* This command never recurses */
            pass = AT_PASS_MISC;
            break;
+       case AT_AlterCollationRefreshVersion:   /* ALTER COLLATION ... REFRESH
+                                                * VERSION */
+           ATSimplePermissions(rel, ATT_INDEX);
+           /* This command never recurses */
+           pass = AT_PASS_MISC;
+           break;
        case AT_SetStorage:     /* ALTER COLUMN SET STORAGE */
            ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE);
            ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context);
@@ -4738,6 +4750,11 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
            Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
            ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
            break;
+       case AT_AlterCollationRefreshVersion:
+           /* ATPrepCmd ensured it must be an index */
+           Assert(rel->rd_rel->relkind == RELKIND_INDEX);
+           ATExecAlterCollationRefreshVersion(rel, cmd->object);
+           break;
        default:                /* oops */
            elog(ERROR, "unrecognized alter table type: %d",
                 (int) cmd->subtype);
@@ -17582,3 +17599,17 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
        table_close(rel, NoLock);
    }
 }
+
+/*
+ * ALTER INDEX ... ALTER COLLATION ... REFRESH VERSION
+ *
+ * Update refobjversion to the current collation version by force.  This clears
+ * warnings about version mismatches without the need to run REINDEX,
+ * potentially hiding corruption due to ordering changes.
+ */
+static void
+ATExecAlterCollationRefreshVersion(Relation rel, List *coll)
+{
+   index_update_collation_versions(rel->rd_id, get_collation_oid(coll, false));
+   CacheInvalidateRelcache(rel);
+}
index ac8b57109c5652c349e775bfbca24f0b752563a7..530aac68a7620df3915e83ec4cedffa2db69d45b 100644 (file)
@@ -3215,6 +3215,7 @@ _copyAlterTableCmd(const AlterTableCmd *from)
 
    COPY_SCALAR_FIELD(subtype);
    COPY_STRING_FIELD(name);
+   COPY_NODE_FIELD(object);
    COPY_SCALAR_FIELD(num);
    COPY_NODE_FIELD(newowner);
    COPY_NODE_FIELD(def);
index 4f0da51c2697c8e57faeaac5ad7da3e411cfb9c0..52c01eb86b17a7deb4c33d6cc023d9336198e1f5 100644 (file)
@@ -28,6 +28,7 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/heap.h"
+#include "catalog/index.h"
 #include "catalog/pg_am.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_statistic_ext.h"
@@ -198,6 +199,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
            indexRelation = index_open(indexoid, lmode);
            index = indexRelation->rd_index;
 
+           /* Warn if any dependent collations' versions have moved. */
+           if (!IsSystemRelation(relation) &&
+               !indexRelation->rd_version_checked)
+           {
+               index_check_collation_versions(indexoid);
+               indexRelation->rd_version_checked = true;
+           }
+
            /*
             * Ignore invalid indexes, since they can't safely be used for
             * queries.  Note that this is OK because the data structure we
index 60cf7242a30845e1c674a3fb141557bcd4737570..357ab93fb65038e2ac37f19b058c747eb8a55e34 100644 (file)
@@ -2591,6 +2591,14 @@ alter_table_cmd:
                    n->subtype = AT_NoForceRowSecurity;
                    $$ = (Node *)n;
                }
+           /* ALTER INDEX <name> ALTER COLLATION ... REFRESH VERSION */
+           | ALTER COLLATION any_name REFRESH VERSION_P
+               {
+                   AlterTableCmd *n = makeNode(AlterTableCmd);
+                   n->subtype = AT_AlterCollationRefreshVersion;
+                   n->object = $3;
+                   $$ = (Node *)n;
+               }
            | alter_generic_options
                {
                    AlterTableCmd *n = makeNode(AlterTableCmd);
index 514e0fa0af866e5b0d5b3a11d826c2f0315ab6c9..3b0324ce18ab76c976fa61299c9dc2db56a8f34d 100644 (file)
@@ -57,7 +57,9 @@
 #include "access/htup_details.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_control.h"
+#include "catalog/pg_database.h"
 #include "mb/pg_wchar.h"
+#include "miscadmin.h"
 #include "utils/builtins.h"
 #include "utils/formatting.h"
 #include "utils/hsearch.h"
@@ -139,6 +141,9 @@ static char *IsoLocaleName(const char *);   /* MSVC specific */
 static void icu_set_collation_attributes(UCollator *collator, const char *loc);
 #endif
 
+static char *get_collation_actual_version(char collprovider,
+                                         const char *collcollate);
+
 /*
  * pg_perm_setlocale
  *
@@ -1630,7 +1635,7 @@ pg_newlocale_from_collation(Oid collid)
  * Get provider-specific collation version string for the given collation from
  * the operating system/library.
  */
-char *
+static char *
 get_collation_actual_version(char collprovider, const char *collcollate)
 {
    char       *collversion = NULL;
@@ -1712,6 +1717,45 @@ get_collation_actual_version(char collprovider, const char *collcollate)
    return collversion;
 }
 
+/*
+ * Get provider-specific collation version string for a given collation OID.
+ * Return NULL if the provider doesn't support versions.
+ */
+char *
+get_collation_version_for_oid(Oid oid)
+{
+   HeapTuple   tp;
+   char       *version = NULL;
+
+   Assert(oid != C_COLLATION_OID && oid != POSIX_COLLATION_OID);
+
+   if (oid == DEFAULT_COLLATION_OID)
+   {
+       Form_pg_database dbform;
+
+       tp = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId));
+       if (!HeapTupleIsValid(tp))
+           elog(ERROR, "cache lookup failed for database %u", MyDatabaseId);
+       dbform = (Form_pg_database) GETSTRUCT(tp);
+       version = get_collation_actual_version(COLLPROVIDER_LIBC,
+                                              NameStr(dbform->datcollate));
+   }
+   else
+   {
+       Form_pg_collation collform;
+
+       tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(oid));
+       if (!HeapTupleIsValid(tp))
+           elog(ERROR, "cache lookup failed for collation %u", oid);
+       collform = (Form_pg_collation) GETSTRUCT(tp);
+       version = get_collation_actual_version(collform->collprovider,
+                                              NameStr(collform->collcollate));
+   }
+
+   ReleaseSysCache(tp);
+
+   return version;
+}
 
 #ifdef USE_ICU
 /*
index 14d9eb2b5b3db92d2b2317a842175a8e9a90fb5d..8a5953881eebd7794262acceabda3b31b63493e7 100644 (file)
@@ -13,6 +13,7 @@
 
 #include "catalog/binary_upgrade.h"
 #include "catalog/heap.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
 #include "commands/extension.h"
index 9224e2ffeda593dafb39e1d6dfcfca7f0c652781..66393becfb6821f9bc3cbc23fefd9da212c9f214 100644 (file)
@@ -42,6 +42,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/catalog.h"
+#include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/partition.h"
@@ -5934,6 +5935,7 @@ load_relcache_init_file(bool shared)
        rel->rd_idattr = NULL;
        rel->rd_pubactions = NULL;
        rel->rd_statvalid = false;
+       rel->rd_version_checked = false;
        rel->rd_statlist = NIL;
        rel->rd_fkeyvalid = false;
        rel->rd_fkeylist = NIL;
index 6c79319164144107ccc7cfe5e393798b4ca825d3..4d49d2b96af0f039f39787f32680698e1f28f33e 100644 (file)
@@ -179,6 +179,7 @@ typedef struct _dumpOptions
 
    int         sequence_data;  /* dump sequence data even in schema-only mode */
    int         do_nothing;
+   int         coll_unknown;
 } DumpOptions;
 
 /*
index 9851022a53d426a6f266e01ad2d9a501f17700c6..cf89a7ec6d7717d6a6726eb4319f7a8f34ac088c 100644 (file)
@@ -46,6 +46,7 @@
 #include "catalog/pg_attribute_d.h"
 #include "catalog/pg_cast_d.h"
 #include "catalog/pg_class_d.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_default_acl_d.h"
 #include "catalog/pg_largeobject_d.h"
 #include "catalog/pg_largeobject_metadata_d.h"
@@ -285,6 +286,9 @@ static void binary_upgrade_extension_member(PQExpBuffer upgrade_buffer,
 static const char *getAttrName(int attrnum, TableInfo *tblInfo);
 static const char *fmtCopyColumnList(const TableInfo *ti, PQExpBuffer buffer);
 static bool nonemptyReloptions(const char *reloptions);
+static void appendIndexCollationVersion(PQExpBuffer buffer, IndxInfo *indxinfo,
+                                       int enc, bool coll_unknown,
+                                       Archive *fount);
 static void appendReloptionsArrayAH(PQExpBuffer buffer, const char *reloptions,
                                    const char *prefix, Archive *fout);
 static char *get_synchronized_snapshot(Archive *fout);
@@ -385,6 +389,7 @@ main(int argc, char **argv)
        {"on-conflict-do-nothing", no_argument, &dopt.do_nothing, 1},
        {"rows-per-insert", required_argument, NULL, 10},
        {"include-foreign-data", required_argument, NULL, 11},
+       {"index-collation-versions-unknown", no_argument, &dopt.coll_unknown, 1},
 
        {NULL, 0, NULL, 0}
    };
@@ -712,6 +717,10 @@ main(int argc, char **argv)
    if (archiveFormat != archDirectory && numWorkers > 1)
        fatal("parallel backup only supported by the directory format");
 
+   /* Unknown collation versions only relevant in binary upgrade mode */
+   if (dopt.coll_unknown && !dopt.binary_upgrade)
+       fatal("option --index-collation-versions-unknown only works in binary upgrade mode");
+
    /* Open the output file */
    fout = CreateArchive(filename, archiveFormat, compressLevel, dosync,
                         archiveMode, setupDumpWorker);
@@ -7031,7 +7040,9 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
                i_tablespace,
                i_indreloptions,
                i_indstatcols,
-               i_indstatvals;
+               i_indstatvals,
+               i_inddependcollnames,
+               i_inddependcollversions;
    int         ntups;
 
    for (i = 0; i < numTables; i++)
@@ -7067,7 +7078,64 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
         * is not.
         */
        resetPQExpBuffer(query);
-       if (fout->remoteVersion >= 110000)
+       if (fout->remoteVersion >= 140000)
+       {
+           appendPQExpBuffer(query,
+                             "SELECT t.tableoid, t.oid, "
+                             "t.relname AS indexname, "
+                             "inh.inhparent AS parentidx, "
+                             "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
+                             "i.indnkeyatts AS indnkeyatts, "
+                             "i.indnatts AS indnatts, "
+                             "i.indkey, i.indisclustered, "
+                             "i.indisreplident, "
+                             "c.contype, c.conname, "
+                             "c.condeferrable, c.condeferred, "
+                             "c.tableoid AS contableoid, "
+                             "c.oid AS conoid, "
+                             "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
+                             "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
+                             "t.reloptions AS indreloptions, "
+                             "(SELECT pg_catalog.array_agg(attnum ORDER BY attnum) "
+                             "  FROM pg_catalog.pg_attribute "
+                             "  WHERE attrelid = i.indexrelid AND "
+                             "    attstattarget >= 0) AS indstatcols,"
+                             "(SELECT pg_catalog.array_agg(attstattarget ORDER BY attnum) "
+                             "  FROM pg_catalog.pg_attribute "
+                             "  WHERE attrelid = i.indexrelid AND "
+                             "    attstattarget >= 0) AS indstatvals, "
+                             "(SELECT pg_catalog.array_agg(quote_ident(ns.nspname) || '.' || quote_ident(c.collname) ORDER BY refobjid) "
+                             "  FROM pg_catalog.pg_depend d "
+                             "  JOIN pg_catalog.pg_collation c ON (c.oid = d.refobjid) "
+                             "  JOIN pg_catalog.pg_namespace ns ON (c.collnamespace = ns.oid) "
+                             "  WHERE d.classid = 'pg_catalog.pg_class'::regclass AND "
+                             "    d.objid = i.indexrelid AND "
+                             "    d.objsubid = 0 AND "
+                             "    d.refclassid = 'pg_catalog.pg_collation'::regclass AND "
+                             "    d.refobjversion IS NOT NULL) AS inddependcollnames, "
+                             "(SELECT pg_catalog.array_agg(quote_literal(refobjversion) ORDER BY refobjid) "
+                             "  FROM pg_catalog.pg_depend "
+                             "  WHERE classid = 'pg_catalog.pg_class'::regclass AND "
+                             "    objid = i.indexrelid AND "
+                             "    objsubid = 0 AND "
+                             "    refclassid = 'pg_catalog.pg_collation'::regclass AND "
+                             "    refobjversion IS NOT NULL) AS inddependcollversions "
+                             "FROM pg_catalog.pg_index i "
+                             "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
+                             "JOIN pg_catalog.pg_class t2 ON (t2.oid = i.indrelid) "
+                             "LEFT JOIN pg_catalog.pg_constraint c "
+                             "ON (i.indrelid = c.conrelid AND "
+                             "i.indexrelid = c.conindid AND "
+                             "c.contype IN ('p','u','x')) "
+                             "LEFT JOIN pg_catalog.pg_inherits inh "
+                             "ON (inh.inhrelid = indexrelid) "
+                             "WHERE i.indrelid = '%u'::pg_catalog.oid "
+                             "AND (i.indisvalid OR t2.relkind = 'p') "
+                             "AND i.indisready "
+                             "ORDER BY indexname",
+                             tbinfo->dobj.catId.oid);
+       }
+       else if (fout->remoteVersion >= 110000)
        {
            appendPQExpBuffer(query,
                              "SELECT t.tableoid, t.oid, "
@@ -7092,7 +7160,9 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
                              "(SELECT pg_catalog.array_agg(attstattarget ORDER BY attnum) "
                              "  FROM pg_catalog.pg_attribute "
                              "  WHERE attrelid = i.indexrelid AND "
-                             "    attstattarget >= 0) AS indstatvals "
+                             "    attstattarget >= 0) AS indstatvals, "
+                             "'{}' AS inddependcollnames, "
+                             "'{}' AS inddependcollversions "
                              "FROM pg_catalog.pg_index i "
                              "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
                              "JOIN pg_catalog.pg_class t2 ON (t2.oid = i.indrelid) "
@@ -7131,7 +7201,9 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
                              "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
                              "t.reloptions AS indreloptions, "
                              "'' AS indstatcols, "
-                             "'' AS indstatvals "
+                             "'' AS indstatvals, "
+                             "'{}' AS inddependcollnames, "
+                             "'{}' AS inddependcollversions "
                              "FROM pg_catalog.pg_index i "
                              "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
                              "LEFT JOIN pg_catalog.pg_constraint c "
@@ -7166,7 +7238,9 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
                              "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
                              "t.reloptions AS indreloptions, "
                              "'' AS indstatcols, "
-                             "'' AS indstatvals "
+                             "'' AS indstatvals, "
+                             "'{}' AS inddependcollnames, "
+                             "'{}' AS inddependcollversions "
                              "FROM pg_catalog.pg_index i "
                              "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
                              "LEFT JOIN pg_catalog.pg_constraint c "
@@ -7197,7 +7271,9 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
                              "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
                              "t.reloptions AS indreloptions, "
                              "'' AS indstatcols, "
-                             "'' AS indstatvals "
+                             "'' AS indstatvals, "
+                             "'{}' AS inddependcollnames, "
+                             "'{}' AS inddependcollversions "
                              "FROM pg_catalog.pg_index i "
                              "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
                              "LEFT JOIN pg_catalog.pg_depend d "
@@ -7231,7 +7307,9 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
                              "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
                              "null AS indreloptions, "
                              "'' AS indstatcols, "
-                             "'' AS indstatvals "
+                             "'' AS indstatvals, "
+                             "'{}' AS inddependcollnames, "
+                             "'{}' AS inddependcollversions "
                              "FROM pg_catalog.pg_index i "
                              "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
                              "LEFT JOIN pg_catalog.pg_depend d "
@@ -7271,6 +7349,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
        i_indreloptions = PQfnumber(res, "indreloptions");
        i_indstatcols = PQfnumber(res, "indstatcols");
        i_indstatvals = PQfnumber(res, "indstatvals");
+       i_inddependcollnames = PQfnumber(res, "inddependcollnames");
+       i_inddependcollversions = PQfnumber(res, "inddependcollversions");
 
        tbinfo->indexes = indxinfo =
            (IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo));
@@ -7296,6 +7376,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
            indxinfo[j].indreloptions = pg_strdup(PQgetvalue(res, j, i_indreloptions));
            indxinfo[j].indstatcols = pg_strdup(PQgetvalue(res, j, i_indstatcols));
            indxinfo[j].indstatvals = pg_strdup(PQgetvalue(res, j, i_indstatvals));
+           indxinfo[j].inddependcollnames = pg_strdup(PQgetvalue(res, j, i_inddependcollnames));
+           indxinfo[j].inddependcollversions = pg_strdup(PQgetvalue(res, j, i_inddependcollversions));
            indxinfo[j].indkeys = (Oid *) pg_malloc(indxinfo[j].indnattrs * sizeof(Oid));
            parseOidArray(PQgetvalue(res, j, i_indkey),
                          indxinfo[j].indkeys, indxinfo[j].indnattrs);
@@ -16362,7 +16444,8 @@ dumpIndex(Archive *fout, IndxInfo *indxinfo)
 
    /*
     * If there's an associated constraint, don't dump the index per se, but
-    * do dump any comment for it.  (This is safe because dependency ordering
+    * do dump any comment, or in binary upgrade mode dependency on a
+    * collation version for it.  (This is safe because dependency ordering
     * will have ensured the constraint is emitted first.)  Note that the
     * emitted comment has to be shown as depending on the constraint, not the
     * index, in such cases.
@@ -16429,6 +16512,10 @@ dumpIndex(Archive *fout, IndxInfo *indxinfo)
                                    "pg_catalog.pg_class",
                                    "INDEX", qqindxname);
 
+       if (dopt->binary_upgrade)
+           appendIndexCollationVersion(q, indxinfo, fout->encoding,
+                                       dopt->coll_unknown, fout);
+
        /* If the index defines identity, we need to record that. */
        if (indxinfo->indisreplident)
        {
@@ -16457,6 +16544,21 @@ dumpIndex(Archive *fout, IndxInfo *indxinfo)
        if (indstatvalsarray)
            free(indstatvalsarray);
    }
+   else if (dopt->binary_upgrade)
+   {
+       appendIndexCollationVersion(q, indxinfo, fout->encoding,
+                                   dopt->coll_unknown, fout);
+
+       if (indxinfo->dobj.dump & DUMP_COMPONENT_DEFINITION)
+           ArchiveEntry(fout, indxinfo->dobj.catId, indxinfo->dobj.dumpId,
+                        ARCHIVE_OPTS(.tag = indxinfo->dobj.name,
+                                     .namespace = tbinfo->dobj.namespace->dobj.name,
+                                     .tablespace = indxinfo->tablespace,
+                                     .owner = tbinfo->rolname,
+                                     .description = "INDEX",
+                                     .section = SECTION_POST_DATA,
+                                     .createStmt = q->data));
+   }
 
    /* Dump Index Comments */
    if (indxinfo->dobj.dump & DUMP_COMPONENT_COMMENT)
@@ -18441,6 +18543,70 @@ nonemptyReloptions(const char *reloptions)
    return (reloptions != NULL && strlen(reloptions) > 2);
 }
 
+/*
+ * Generate UPDATE statements to import the collation versions into the new
+ * cluster, during a binary upgrade.
+ */
+static void
+appendIndexCollationVersion(PQExpBuffer buffer, IndxInfo *indxinfo, int enc,
+                           bool coll_unknown, Archive *fout)
+{
+   char       *inddependcollnames = indxinfo->inddependcollnames;
+   char       *inddependcollversions = indxinfo->inddependcollversions;
+   char      **inddependcollnamesarray;
+   char      **inddependcollversionsarray;
+   int         ninddependcollnames;
+   int         ninddependcollversions;
+
+   /*
+    * By default, the new cluster's index will have pg_depends rows with
+    * current collation versions, meaning that we assume the index isn't
+    * corrupted if importing from a release that didn't record versions.
+    * However, if --index-collation-versions-unknown was passed in, then we
+    * assume such indexes might be corrupted, and clobber versions with
+    * 'unknown' to trigger version warnings.
+    */
+   if (coll_unknown)
+   {
+       appendPQExpBuffer(buffer,
+                         "\n-- For binary upgrade, clobber new index's collation versions\n");
+       appendPQExpBuffer(buffer,
+                         "UPDATE pg_catalog.pg_depend SET refobjversion = 'unknown' WHERE objid = '%u'::pg_catalog.oid AND refclassid = 'pg_catalog.pg_collation'::regclass AND refobjversion IS NOT NULL;\n",
+                         indxinfo->dobj.catId.oid);
+   }
+
+   /* Restore the versions that were recorded by the old cluster (if any). */
+   parsePGArray(inddependcollnames,
+                &inddependcollnamesarray,
+                &ninddependcollnames);
+   parsePGArray(inddependcollversions,
+                &inddependcollversionsarray,
+                &ninddependcollversions);
+   Assert(ninddependcollnames == ninddependcollversions);
+
+   if (ninddependcollnames > 0)
+       appendPQExpBufferStr(buffer,
+                            "\n-- For binary upgrade, restore old index's collation versions\n");
+   for (int i = 0; i < ninddependcollnames; i++)
+   {
+       /*
+        * Import refobjversion from the old cluster, being careful to resolve
+        * the collation OID by name in the new cluster.
+        */
+       appendPQExpBuffer(buffer,
+                         "UPDATE pg_catalog.pg_depend SET refobjversion = %s WHERE objid = '%u'::pg_catalog.oid AND refclassid = 'pg_catalog.pg_collation'::regclass AND refobjversion IS NOT NULL AND refobjid = ",
+                         inddependcollversionsarray[i],
+                         indxinfo->dobj.catId.oid);
+       appendStringLiteralAH(buffer,inddependcollnamesarray[i], fout);
+       appendPQExpBuffer(buffer, "::regcollation;\n");
+   }
+
+   if (inddependcollnamesarray)
+       free(inddependcollnamesarray);
+   if (inddependcollversionsarray)
+       free(inddependcollversionsarray);
+}
+
 /*
  * Format a reloptions array and append it to the given buffer.
  *
index e0b42e83912fab74c1ca8e20a640d0f1c85686f1..317bb839702e76d4dd4822d58c6f4a024f9a5e3f 100644 (file)
@@ -366,6 +366,8 @@ typedef struct _indxInfo
    int         indnattrs;      /* total number of index attributes */
    Oid        *indkeys;        /* In spite of the name 'indkeys' this field
                                 * contains both key and nonkey attributes */
+   char       *inddependcollnames; /* FQ names of depended-on collations */
+   char       *inddependcollversions;  /* versions of the above */
    bool        indisclustered;
    bool        indisreplident;
    Oid         parentidx;      /* if a partition, parent index OID */
index 4d730adfe24edd4454cb1109b2b4d35b61603b2d..20e73be361538b5def6fb7014795449560083eda 100644 (file)
@@ -52,9 +52,11 @@ generate_old_dump(void)
 
        parallel_exec_prog(log_file_name, NULL,
                           "\"%s/pg_dump\" %s --schema-only --quote-all-identifiers "
-                          "--binary-upgrade --format=custom %s --file=\"%s\" %s",
+                          "--binary-upgrade --format=custom %s %s --file=\"%s\" %s",
                           new_cluster.bindir, cluster_conn_opts(&old_cluster),
                           log_opts.verbose ? "--verbose" : "",
+                          user_opts.ind_coll_unknown ?
+                          "--index-collation-versions-unknown" : "",
                           sql_file_name, escaped_connstr.data);
 
        termPQExpBuffer(&escaped_connstr);
index aca1ee8b48d810223baed2e3b512462a97489993..548d648e8c4e6fcf20935bfab8e76922a04c2235 100644 (file)
@@ -56,6 +56,7 @@ parseCommandLine(int argc, char *argv[])
        {"socketdir", required_argument, NULL, 's'},
        {"verbose", no_argument, NULL, 'v'},
        {"clone", no_argument, NULL, 1},
+       {"index-collation-versions-unknown", no_argument, NULL, 2},
 
        {NULL, 0, NULL, 0}
    };
@@ -203,6 +204,10 @@ parseCommandLine(int argc, char *argv[])
                user_opts.transfer_mode = TRANSFER_MODE_CLONE;
                break;
 
+           case 2:
+               user_opts.ind_coll_unknown = true;
+               break;
+
            default:
                fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
                        os_info.progname);
@@ -307,6 +312,8 @@ usage(void)
    printf(_("  -v, --verbose                 enable verbose internal logging\n"));
    printf(_("  -V, --version                 display version information, then exit\n"));
    printf(_("  --clone                       clone instead of copying files to new cluster\n"));
+   printf(_("  --index-collation-versions-unknown\n"));
+   printf(_("                                mark text indexes as needing to be rebuilt\n"));
    printf(_("  -?, --help                    show this help, then exit\n"));
    printf(_("\n"
             "Before running pg_upgrade you must:\n"
index 8b90cefbe0989e35c96be66f4f46447365cfbd18..19c64513b06a8f84bf2239ae1955fceed0109b17 100644 (file)
@@ -292,6 +292,7 @@ typedef struct
    transferMode transfer_mode; /* copy files or link them? */
    int         jobs;           /* number of processes/threads to use */
    char       *socketdir;      /* directory to use for Unix sockets */
+   bool        ind_coll_unknown;   /* mark unknown index collation versions */
 } UserOpts;
 
 typedef struct
index b2b4f1fd4d13d99ebfe10a1e5b83201107613da5..5238a960f7e5b4753aff0bd079a6f7d88d237d16 100644 (file)
@@ -45,6 +45,7 @@
 
 #include "catalog/pg_am_d.h"
 #include "catalog/pg_class_d.h"
+#include "catalog/pg_collation_d.h"
 #include "common.h"
 #include "libpq-fe.h"
 #include "pqexpbuffer.h"
@@ -820,6 +821,20 @@ static const SchemaQuery Query_for_list_of_statistics = {
 "       (SELECT tgrelid FROM pg_catalog.pg_trigger "\
 "         WHERE pg_catalog.quote_ident(tgname)='%s')"
 
+/* the silly-looking length condition is just to eat up the current word */
+#define Query_for_list_of_colls_for_one_index \
+" SELECT DISTINCT pg_catalog.quote_ident(coll.collname) " \
+"   FROM pg_catalog.pg_depend d, pg_catalog.pg_collation coll, " \
+"     pg_catalog.pg_class c" \
+" WHERE (%d = pg_catalog.length('%s'))" \
+"   AND d.refclassid = " CppAsString2(CollationRelationId) \
+"   AND d.refobjid = coll.oid " \
+"   AND d.classid = " CppAsString2(RelationRelationId) \
+"   AND d.objid = c.oid " \
+"   AND c.relkind = " CppAsString2(RELKIND_INDEX) \
+"   AND pg_catalog.pg_table_is_visible(c.oid) " \
+"   AND c.relname = '%s'"
+
 #define Query_for_list_of_ts_configurations \
 "SELECT pg_catalog.quote_ident(cfgname) FROM pg_catalog.pg_ts_config "\
 " WHERE substring(pg_catalog.quote_ident(cfgname),1,%d)='%s'"
@@ -1715,14 +1730,15 @@ psql_completion(const char *text, int start, int end)
    /* ALTER INDEX <name> */
    else if (Matches("ALTER", "INDEX", MatchAny))
        COMPLETE_WITH("ALTER COLUMN", "OWNER TO", "RENAME TO", "SET",
-                     "RESET", "ATTACH PARTITION", "DEPENDS", "NO DEPENDS");
+                     "RESET", "ATTACH PARTITION", "DEPENDS", "NO DEPENDS",
+                     "ALTER COLLATION");
    else if (Matches("ALTER", "INDEX", MatchAny, "ATTACH"))
        COMPLETE_WITH("PARTITION");
    else if (Matches("ALTER", "INDEX", MatchAny, "ATTACH", "PARTITION"))
        COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
    /* ALTER INDEX <name> ALTER */
    else if (Matches("ALTER", "INDEX", MatchAny, "ALTER"))
-       COMPLETE_WITH("COLUMN");
+       COMPLETE_WITH("COLLATION", "COLUMN");
    /* ALTER INDEX <name> ALTER COLUMN */
    else if (Matches("ALTER", "INDEX", MatchAny, "ALTER", "COLUMN"))
    {
@@ -1765,6 +1781,15 @@ psql_completion(const char *text, int start, int end)
        COMPLETE_WITH("ON EXTENSION");
    else if (Matches("ALTER", "INDEX", MatchAny, "DEPENDS"))
        COMPLETE_WITH("ON EXTENSION");
+   /* ALTER INDEX <name> ALTER COLLATION */
+   else if (Matches("ALTER", "INDEX", MatchAny, "ALTER", "COLLATION"))
+   {
+       completion_info_charp = prev3_wd;
+       COMPLETE_WITH_QUERY(Query_for_list_of_colls_for_one_index);
+   }
+   /* ALTER INDEX <name> ALTER COLLATION <name> */
+   else if (Matches("ALTER", "INDEX", MatchAny, "ALTER", "COLLATION", MatchAny))
+       COMPLETE_WITH("REFRESH VERSION");
 
    /* ALTER LANGUAGE <name> */
    else if (Matches("ALTER", "LANGUAGE", MatchAny))
index 6610e3c23f71716a2971e8c20f97cfecd41a06f0..f28f083aca7bd502e227d30c1ff3e44f0487c8d4 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 202011012
+#define CATALOG_VERSION_NO 202011013
 
 #endif
index 3baa5e498aa9b145a61ae59dfe33c1cd7a301e05..901d5019cdfd5cb8a743ba69f03df812ac1c37e4 100644 (file)
@@ -160,7 +160,8 @@ extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
                                            Node *expr, Oid relId,
                                            DependencyType behavior,
                                            DependencyType self_behavior,
-                                           bool reverse_self);
+                                           bool reverse_self,
+                                           bool record_version);
 
 extern ObjectClass getObjectClass(const ObjectAddress *object);
 
@@ -180,17 +181,30 @@ extern void sort_object_addresses(ObjectAddresses *addrs);
 
 extern void free_object_addresses(ObjectAddresses *addrs);
 
+typedef bool(*VisitDependenciesOfCB) (const ObjectAddress *otherObject,
+                                     const char *version,
+                                     char **new_version,
+                                     void *data);
+
+extern void visitDependenciesOf(const ObjectAddress *object,
+                               VisitDependenciesOfCB callback,
+                               void *data);
+
 /* in pg_depend.c */
 
 extern void recordDependencyOn(const ObjectAddress *depender,
                               const ObjectAddress *referenced,
                               DependencyType behavior);
 
+extern void recordDependencyOnCollations(ObjectAddress *myself,
+                                        List *collations,
+                                        bool record_version);
+
 extern void recordMultipleDependencies(const ObjectAddress *depender,
                                       const ObjectAddress *referenced,
                                       int nreferenced,
-                                      const char *version,
-                                      DependencyType behavior);
+                                      DependencyType behavior,
+                                      bool record_version);
 
 extern void recordDependencyOnCurrentExtension(const ObjectAddress *object,
                                               bool isReplace);
@@ -209,10 +223,9 @@ extern long changeDependencyFor(Oid classId, Oid objectId,
                                Oid refClassId, Oid oldRefObjectId,
                                Oid newRefObjectId);
 
-extern long changeDependenciesOf(Oid classId, Oid oldObjectId,
+long changeDependenciesOf(Oid classId, Oid oldObjectId,
                                 Oid newObjectId);
-
-extern long changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
+long changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
                                 Oid newRefObjectId);
 
 extern Oid getExtensionOfObject(Oid classId, Oid objectId);
index f58e8675f32a51054bfbfbb24f15cd8c8da2cea9..f4559b09d73556cbabcd63e5b6dc8626903bfb81 100644 (file)
@@ -121,6 +121,9 @@ extern void FormIndexDatum(IndexInfo *indexInfo,
                           Datum *values,
                           bool *isnull);
 
+extern void index_check_collation_versions(Oid relid);
+extern void index_update_collation_versions(Oid relid, Oid coll);
+
 extern void index_build(Relation heapRelation,
                        Relation indexRelation,
                        IndexInfo *indexInfo,
index 7489022795296055ff2bf45333f2fd9a2933ee22..eeafbbe8d7c86bdfb1679a9cf4292f34bdc918be 100644 (file)
@@ -62,8 +62,7 @@ CATALOG(pg_depend,2608,DependRelationId)
     */
    char        deptype;        /* see codes in dependency.h */
 #ifdef CATALOG_VARLEN
-   text        refobjversion;  /* version tracking, NULL if not used or
-                                * unknown */
+   text        refobjversion;  /* version of referenced object */
 #endif
 } FormData_pg_depend;
 
index 6ae6edf7e0e819ddd88eb8cd78d21ddbc1d64e8e..d228efffc9b320de9e9911abeba55e9a14277ca5 100644 (file)
@@ -368,6 +368,8 @@ extern void GenerateTypeDependencies(HeapTuple typeTuple,
                                     bool isDependentType,
                                     bool rebuild);
 
+extern List *GetTypeCollations(Oid typeObjectid);
+
 extern void RenameTypeInternal(Oid typeOid, const char *newTypeName,
                               Oid typeNamespace);
 
index 319f77013f4d3ac6e7d84435fcc7cf58fcd20f8e..e1aeea25606285a23f53f3505995756ce1c5ddd9 100644 (file)
@@ -1853,7 +1853,8 @@ typedef enum AlterTableType
    AT_DetachPartition,         /* DETACH PARTITION */
    AT_AddIdentity,             /* ADD IDENTITY */
    AT_SetIdentity,             /* SET identity column options */
-   AT_DropIdentity             /* DROP IDENTITY */
+   AT_DropIdentity,            /* DROP IDENTITY */
+   AT_AlterCollationRefreshVersion /* ALTER COLLATION ... REFRESH VERSION */
 } AlterTableType;
 
 typedef struct ReplicaIdentityStmt
@@ -1869,6 +1870,7 @@ typedef struct AlterTableCmd  /* one subcommand of an ALTER TABLE */
    AlterTableType subtype;     /* Type of table alteration to apply */
    char       *name;           /* column, constraint, or trigger to act on,
                                 * or tablespace */
+   List       *object;         /* collation to act on if it's a collation */
    int16       num;            /* attribute number for columns referenced by
                                 * number */
    RoleSpec   *newowner;
index 9cb7d91ddfb186f823d8a217b04d7e201e8aa4ae..96da132c031bb974d8606a2b175c6cf64b3dbe08 100644 (file)
@@ -103,7 +103,7 @@ typedef struct pg_locale_struct *pg_locale_t;
 
 extern pg_locale_t pg_newlocale_from_collation(Oid collid);
 
-extern char *get_collation_actual_version(char collprovider, const char *collcollate);
+extern char *get_collation_version_for_oid(Oid collid);
 
 #ifdef USE_ICU
 extern int32_t icu_to_uchar(UChar **buff_uchar, const char *buff, size_t nbytes);
index 0b5957ba02c759240af13cd5131737528f67af52..c5ffea40f212747c0026728bddc486e7775bfa62 100644 (file)
@@ -63,6 +63,7 @@ typedef struct RelationData
    bool        rd_indexvalid;  /* is rd_indexlist valid? (also rd_pkindex and
                                 * rd_replidindex) */
    bool        rd_statvalid;   /* is rd_statlist valid? */
+   bool        rd_version_checked; /* has version check been done yet? */
 
    /*----------
     * rd_createSubid is the ID of the highest subtransaction the rel has
index 9774f534d93f87cb3ac53cf36e4d1f52e083ee61..14cde4f5bacd1e58017f7e0c2f6c4483f6083d8f 100644 (file)
@@ -12,7 +12,8 @@ subdir = src/test
 top_builddir = ../..
 include $(top_builddir)/src/Makefile.global
 
-SUBDIRS = perl regress isolation modules authentication recovery subscription
+SUBDIRS = perl regress isolation modules authentication recovery subscription \
+     locale
 
 # Test suites that are not safe by default but can be run if selected
 # by the user via the whitespace-separated list in variable
index 620d3df425421a0f5e423e2c947f5a55aee51a96..64e1bf2a8037cbe5e1cab39b5f53529282c08cec 100644 (file)
@@ -1 +1,2 @@
 /test-ctype
+/tmp_check/
index 22a45b65f2c76bfdebfa1092b340dc16576a6d49..73495cf16b442413420fc91374e3d900d1fd6fe8 100644 (file)
@@ -4,6 +4,7 @@ subdir = src/test/locale
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
+export with_icu
 
 PROGS = test-ctype
 DIRS = de_DE.ISO8859-1 gr_GR.ISO8859-7 koi8-r koi8-to-win1251
@@ -19,3 +20,9 @@ clean distclean maintainer-clean:
 # These behave like installcheck targets.
 check-%: all
    @$(MAKE) -C `echo $@ | sed 's/^check-//'` test
+
+check:
+   $(prove_check)
+
+installcheck:
+   $(prove_installcheck)
diff --git a/src/test/locale/t/001_index.pl b/src/test/locale/t/001_index.pl
new file mode 100644 (file)
index 0000000..a67f78c
--- /dev/null
@@ -0,0 +1,67 @@
+use strict;
+use warnings;
+
+use Config;
+use PostgresNode;
+use TestLib;
+use Test::More;
+
+if ($ENV{with_icu} eq 'yes')
+{
+   plan tests => 10;
+}
+else
+{
+   plan skip_all => 'ICU not supported by this build';
+}
+
+#### Set up the server
+
+note "setting up data directory";
+my $node = get_new_node('main');
+$node->init(extra => [ '--encoding=UTF8' ]);
+
+$ENV{PGHOST} = $node->host;
+$ENV{PGPORT} = $node->port;
+$node->start;
+
+sub test_index
+{
+   my ($err_like, $err_comm) = @_;
+   my ($ret, $out, $err) = $node->psql('postgres', "SELECT * FROM icu1");
+   is($ret, 0, 'SELECT should succeed.');
+   like($err, $err_like, $err_comm);
+}
+
+$node->safe_psql('postgres', 'CREATE TABLE icu1(val text);');
+$node->safe_psql('postgres', 'CREATE INDEX icu1_fr ON icu1 (val COLLATE "fr-x-icu");');
+
+test_index(qr/^$/, 'No warning should be raised');
+
+# Simulate different collation version
+$node->safe_psql('postgres',
+   "UPDATE pg_depend SET refobjversion = 'not_a_version'"
+   . " WHERE refobjversion IS NOT NULL"
+   . " AND objid::regclass::text = 'icu1_fr';");
+
+test_index(qr/index "icu1_fr" depends on collation "fr-x-icu" version "not_a_version", but the current version is/,
+   'Different collation version warning should be raised.');
+
+$node->safe_psql('postgres', 'ALTER INDEX icu1_fr ALTER COLLATION "fr-x-icu" REFRESH VERSION;');
+
+test_index(qr/^$/, 'No warning should be raised');
+
+# Simulate different collation version
+$node->safe_psql('postgres',
+   "UPDATE pg_depend SET refobjversion = 'not_a_version'"
+   . " WHERE refobjversion IS NOT NULL"
+   . " AND objid::regclass::text = 'icu1_fr';");
+
+test_index(qr/index "icu1_fr" depends on collation "fr-x-icu" version "not_a_version", but the current version is/,
+   'Different collation version warning should be raised.');
+
+$node->safe_psql('postgres', 'REINDEX TABLE icu1;');
+
+test_index(qr/^$/, 'No warning should be raised');
+
+$node->stop;
index 60d9263a2ffce47d90e8d2f6a3fb686b4d889cc4..16b4d9e2cdc6b4c2f435bd9cba36b76e75fc39ec 100644 (file)
@@ -1897,6 +1897,207 @@ SELECT (SELECT count(*) FROM test33_0) <> (SELECT count(*) FROM test33_1);
  t
 (1 row)
 
+-- collation versioning support
+CREATE TYPE t_en_fr AS (fr text COLLATE "fr-x-icu", en text COLLATE "en-x-icu");
+CREATE DOMAIN d_en_fr AS t_en_fr;
+CREATE DOMAIN d_es AS text COLLATE "es-x-icu";
+CREATE TYPE t_en_fr_ga AS (en_fr t_en_fr, ga text COLLATE "ga-x-icu");
+CREATE DOMAIN d_en_fr_ga AS t_en_fr_ga;
+CREATE TYPE t_custom AS (meh text, meh2 text);
+CREATE DOMAIN d_custom AS t_custom;
+CREATE COLLATION custom (
+    LOCALE = 'fr-x-icu', PROVIDER = 'icu'
+);
+CREATE TYPE myrange AS range (subtype = text);
+CREATE TYPE myrange_en_fr_ga AS range(subtype = t_en_fr_ga);
+CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
+CREATE TABLE collate_test (
+    id integer,
+    val text COLLATE "fr-x-icu",
+    t_en_fr t_en_fr,
+    d_en_fr d_en_fr,
+    d_es d_es,
+    t_en_fr_ga t_en_fr_ga,
+    d_en_fr_ga d_en_fr_ga,
+    d_en_fr_ga_arr d_en_fr_ga[],
+    myrange myrange,
+    myrange_en_fr_ga myrange_en_fr_ga,
+    mood mood
+);
+CREATE INDEX icuidx00_val ON collate_test(val);
+-- shouldn't get duplicated dependencies
+CREATE INDEX icuidx00_val_val ON collate_test(val, val);
+-- shouldn't track version
+CREATE INDEX icuidx00_val_pattern ON collate_test(val text_pattern_ops);
+-- should have single dependency, no version
+CREATE INDEX icuidx00_val_pattern_val_pattern ON collate_test(val text_pattern_ops, val text_pattern_ops);
+-- should have single dependency, with version
+CREATE INDEX icuidx00_val_pattern_val ON collate_test(val text_pattern_ops, val);
+-- should have single dependency, with version
+CREATE INDEX icuidx00_val_val_pattern ON collate_test(val, val text_pattern_ops);
+-- two rows expected, only one a version, because we don't try to merge these yet
+CREATE INDEX icuidx00_val_pattern_where ON collate_test(val text_pattern_ops) WHERE val >= val;
+-- two rows expected with version, because we don't try to merge these yet
+CREATE INDEX icuidx00_val_where ON collate_test(val) WHERE val >= val;
+-- two rows expected with version (expression walker + attribute)
+CREATE INDEX icuidx00_val_pattern_expr ON collate_test(val varchar_pattern_ops, (val || val));
+-- two rows expected, one with a version (expression walker + attribute)
+CREATE INDEX icuidx00_val_pattern_expr_pattern ON collate_test(val varchar_pattern_ops, (val || val) text_pattern_ops);
+-- should have single dependency, with version tracked
+CREATE INDEX icuidx01_t_en_fr__d_es ON collate_test (t_en_fr, d_es);
+CREATE INDEX icuidx02_d_en_fr ON collate_test (d_en_fr);
+CREATE INDEX icuidx03_t_en_fr_ga ON collate_test (t_en_fr_ga);
+CREATE INDEX icuidx04_d_en_fr_ga ON collate_test (d_en_fr_ga);
+CREATE INDEX icuidx05_d_en_fr_ga_arr ON collate_test (d_en_fr_ga_arr);
+CREATE INDEX icuidx06_d_en_fr_ga ON collate_test(id) WHERE (d_en_fr_ga).en_fr.fr = 'foo';
+CREATE INDEX icuidx07_d_en_fr_ga ON collate_test(id) WHERE (d_en_fr_ga).ga = 'foo';
+CREATE INDEX icuidx08_d_en_fr_ga ON collate_test(id) WHERE (t_en_fr_ga) = ('foo', 'bar', 'baz');
+CREATE INDEX icuidx09_d_en_fr_ga ON collate_test(id) WHERE (d_en_fr_ga) = ('foo', 'bar', 'baz');
+CREATE INDEX icuidx10_d_en_fr_ga_es ON collate_test(id) WHERE (d_en_fr_ga) = ('foo', 'bar', 'baz' COLLATE "es-x-icu");
+CREATE INDEX icuidx11_d_es ON collate_test(id) WHERE (d_es) = ('foo');
+CREATE INDEX icuidx12_custom ON collate_test(id) WHERE ('foo', 'bar')::d_custom = ('foo', 'bar' COLLATE custom)::d_custom;
+CREATE INDEX icuidx13_custom ON collate_test(id) WHERE ('foo' COLLATE custom, 'bar')::d_custom = ('foo', 'bar')::d_custom;
+CREATE INDEX icuidx14_myrange ON collate_test(myrange);
+CREATE INDEX icuidx15_myrange_en_fr_ga ON collate_test USING gist (myrange_en_fr_ga);
+CREATE INDEX icuidx16_mood ON collate_test(id) WHERE mood > 'ok' COLLATE "fr-x-icu";
+CREATE TABLE collate_part(id integer, val text COLLATE "en-x-icu") PARTITION BY range(id);
+CREATE TABLE collate_part_0 PARTITION OF collate_part FOR VALUES FROM (0) TO (1);
+CREATE TABLE collate_part_1 PARTITION OF collate_part FOR VALUES FROM (1) TO (1000000);
+CREATE INDEX icuidx17_part ON collate_part_1 (val);
+SELECT objid::regclass::text collate "C", refobjid::regcollation::text collate "C",
+CASE
+WHEN refobjid = 'default'::regcollation THEN 'XXX' -- depends on libc version support
+WHEN refobjversion IS NULL THEN 'version not tracked'
+WHEN refobjversion = pg_collation_actual_version(refobjid) THEN 'up to date'
+ELSE 'out of date'
+END AS version
+FROM pg_depend d
+LEFT JOIN pg_class c ON c.oid = d.objid
+WHERE refclassid = 'pg_collation'::regclass
+AND coalesce(relkind, 'i') = 'i'
+AND relname LIKE 'icuidx%'
+ORDER BY 1, 2, 3;
+               objid               |  refobjid  |       version       
+-----------------------------------+------------+---------------------
+ icuidx00_val                      | "fr-x-icu" | up to date
+ icuidx00_val_pattern              | "fr-x-icu" | version not tracked
+ icuidx00_val_pattern_expr         | "fr-x-icu" | up to date
+ icuidx00_val_pattern_expr         | "fr-x-icu" | up to date
+ icuidx00_val_pattern_expr_pattern | "fr-x-icu" | up to date
+ icuidx00_val_pattern_expr_pattern | "fr-x-icu" | version not tracked
+ icuidx00_val_pattern_val          | "fr-x-icu" | up to date
+ icuidx00_val_pattern_val_pattern  | "fr-x-icu" | version not tracked
+ icuidx00_val_pattern_where        | "fr-x-icu" | up to date
+ icuidx00_val_pattern_where        | "fr-x-icu" | version not tracked
+ icuidx00_val_val                  | "fr-x-icu" | up to date
+ icuidx00_val_val_pattern          | "fr-x-icu" | up to date
+ icuidx00_val_where                | "fr-x-icu" | up to date
+ icuidx00_val_where                | "fr-x-icu" | up to date
+ icuidx01_t_en_fr__d_es            | "en-x-icu" | up to date
+ icuidx01_t_en_fr__d_es            | "es-x-icu" | up to date
+ icuidx01_t_en_fr__d_es            | "fr-x-icu" | up to date
+ icuidx02_d_en_fr                  | "en-x-icu" | up to date
+ icuidx02_d_en_fr                  | "fr-x-icu" | up to date
+ icuidx03_t_en_fr_ga               | "en-x-icu" | up to date
+ icuidx03_t_en_fr_ga               | "fr-x-icu" | up to date
+ icuidx03_t_en_fr_ga               | "ga-x-icu" | up to date
+ icuidx04_d_en_fr_ga               | "en-x-icu" | up to date
+ icuidx04_d_en_fr_ga               | "fr-x-icu" | up to date
+ icuidx04_d_en_fr_ga               | "ga-x-icu" | up to date
+ icuidx05_d_en_fr_ga_arr           | "en-x-icu" | up to date
+ icuidx05_d_en_fr_ga_arr           | "fr-x-icu" | up to date
+ icuidx05_d_en_fr_ga_arr           | "ga-x-icu" | up to date
+ icuidx06_d_en_fr_ga               | "default"  | XXX
+ icuidx06_d_en_fr_ga               | "en-x-icu" | up to date
+ icuidx06_d_en_fr_ga               | "fr-x-icu" | up to date
+ icuidx06_d_en_fr_ga               | "ga-x-icu" | up to date
+ icuidx07_d_en_fr_ga               | "default"  | XXX
+ icuidx07_d_en_fr_ga               | "en-x-icu" | up to date
+ icuidx07_d_en_fr_ga               | "fr-x-icu" | up to date
+ icuidx07_d_en_fr_ga               | "ga-x-icu" | up to date
+ icuidx08_d_en_fr_ga               | "en-x-icu" | up to date
+ icuidx08_d_en_fr_ga               | "fr-x-icu" | up to date
+ icuidx08_d_en_fr_ga               | "ga-x-icu" | up to date
+ icuidx09_d_en_fr_ga               | "en-x-icu" | up to date
+ icuidx09_d_en_fr_ga               | "fr-x-icu" | up to date
+ icuidx09_d_en_fr_ga               | "ga-x-icu" | up to date
+ icuidx10_d_en_fr_ga_es            | "en-x-icu" | up to date
+ icuidx10_d_en_fr_ga_es            | "es-x-icu" | up to date
+ icuidx10_d_en_fr_ga_es            | "fr-x-icu" | up to date
+ icuidx10_d_en_fr_ga_es            | "ga-x-icu" | up to date
+ icuidx11_d_es                     | "default"  | XXX
+ icuidx11_d_es                     | "es-x-icu" | up to date
+ icuidx12_custom                   | "default"  | XXX
+ icuidx12_custom                   | custom     | up to date
+ icuidx13_custom                   | "default"  | XXX
+ icuidx13_custom                   | custom     | up to date
+ icuidx14_myrange                  | "default"  | XXX
+ icuidx15_myrange_en_fr_ga         | "en-x-icu" | up to date
+ icuidx15_myrange_en_fr_ga         | "fr-x-icu" | up to date
+ icuidx15_myrange_en_fr_ga         | "ga-x-icu" | up to date
+ icuidx16_mood                     | "fr-x-icu" | up to date
+ icuidx17_part                     | "en-x-icu" | up to date
+(58 rows)
+
+-- Validate that REINDEX will update the stored version.
+UPDATE pg_depend SET refobjversion = 'not a version'
+WHERE refclassid = 'pg_collation'::regclass
+AND objid::regclass::text LIKE 'icuidx%'
+AND refobjversion IS NOT NULL;
+REINDEX TABLE collate_test;
+REINDEX TABLE collate_part_0;
+REINDEX TABLE collate_part_1;
+SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version';
+ objid 
+-------
+(0 rows)
+
+-- Validate that REINDEX CONCURRENTLY will update the stored version.
+UPDATE pg_depend SET refobjversion = 'not a version'
+WHERE refclassid = 'pg_collation'::regclass
+AND objid::regclass::text LIKE 'icuidx%'
+AND refobjversion IS NOT NULL;
+REINDEX TABLE CONCURRENTLY collate_test;
+REINDEX TABLE CONCURRENTLY collate_part_0;
+REINDEX INDEX CONCURRENTLY icuidx17_part;
+SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version';
+ objid 
+-------
+(0 rows)
+
+-- Validate that VACUUM FULL will update the stored version.
+UPDATE pg_depend SET refobjversion = 'not a version'
+WHERE refclassid = 'pg_collation'::regclass
+AND objid::regclass::text LIKE 'icuidx%'
+AND refobjversion IS NOT NULL;
+VACUUM FULL collate_test;
+VACUUM FULL collate_part_0;
+VACUUM FULL collate_part_1;
+SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version';
+ objid 
+-------
+(0 rows)
+
+-- Test ALTER INDEX name ALTER COLLATION name REFRESH VERSION
+UPDATE pg_depend SET refobjversion = 'not a version'
+WHERE refclassid = 'pg_collation'::regclass
+AND objid::regclass::text = 'icuidx17_part'
+AND refobjversion IS NOT NULL;
+SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version';
+     objid     
+---------------
+ icuidx17_part
+(1 row)
+
+ALTER INDEX icuidx17_part ALTER COLLATION "en-x-icu" REFRESH VERSION;
+SELECT objid::regclass, refobjversion = 'not a version' AS ver FROM pg_depend
+WHERE refclassid = 'pg_collation'::regclass
+AND objid::regclass::text = 'icuidx17_part';
+     objid     | ver 
+---------------+-----
+ icuidx17_part | f
+(1 row)
+
 -- cleanup
 RESET search_path;
 SET client_min_messages TO warning;
index 012c1eb0676cd82b16265891e7ee06c7aa2df9ea..17f1383ea49a260d0bea9e6c13139db05cf39fe0 100644 (file)
@@ -2065,14 +2065,16 @@ WHERE classid = 'pg_class'::regclass AND
                    obj                    |                           objref                           | deptype 
 ------------------------------------------+------------------------------------------------------------+---------
  index concur_reindex_ind1                | constraint concur_reindex_ind1 on table concur_reindex_tab | i
+ index concur_reindex_ind2                | collation "default"                                        | n
  index concur_reindex_ind2                | column c2 of table concur_reindex_tab                      | a
  index concur_reindex_ind3                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind3                | table concur_reindex_tab                                   | a
+ index concur_reindex_ind4                | collation "default"                                        | n
  index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind4                | column c2 of table concur_reindex_tab                      | a
  materialized view concur_reindex_matview | schema public                                              | n
  table concur_reindex_tab                 | schema public                                              | n
-(8 rows)
+(10 rows)
 
 REINDEX INDEX CONCURRENTLY concur_reindex_ind1;
 REINDEX TABLE CONCURRENTLY concur_reindex_tab;
@@ -2092,14 +2094,16 @@ WHERE classid = 'pg_class'::regclass AND
                    obj                    |                           objref                           | deptype 
 ------------------------------------------+------------------------------------------------------------+---------
  index concur_reindex_ind1                | constraint concur_reindex_ind1 on table concur_reindex_tab | i
+ index concur_reindex_ind2                | collation "default"                                        | n
  index concur_reindex_ind2                | column c2 of table concur_reindex_tab                      | a
  index concur_reindex_ind3                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind3                | table concur_reindex_tab                                   | a
+ index concur_reindex_ind4                | collation "default"                                        | n
  index concur_reindex_ind4                | column c1 of table concur_reindex_tab                      | a
  index concur_reindex_ind4                | column c2 of table concur_reindex_tab                      | a
  materialized view concur_reindex_matview | schema public                                              | n
  table concur_reindex_tab                 | schema public                                              | n
-(8 rows)
+(10 rows)
 
 -- Check that comments are preserved
 CREATE TABLE testcomment (i int);
index 35acf91fbf1e49d6e636194959c65987c791f588..4714c044d5316e2b34c864d3db7b7231a1e1f01f 100644 (file)
@@ -716,6 +716,138 @@ INSERT INTO test33 VALUES (2, 'DEF');
 -- they end up in the same partition (but it's platform-dependent which one)
 SELECT (SELECT count(*) FROM test33_0) <> (SELECT count(*) FROM test33_1);
 
+-- collation versioning support
+CREATE TYPE t_en_fr AS (fr text COLLATE "fr-x-icu", en text COLLATE "en-x-icu");
+CREATE DOMAIN d_en_fr AS t_en_fr;
+CREATE DOMAIN d_es AS text COLLATE "es-x-icu";
+CREATE TYPE t_en_fr_ga AS (en_fr t_en_fr, ga text COLLATE "ga-x-icu");
+CREATE DOMAIN d_en_fr_ga AS t_en_fr_ga;
+CREATE TYPE t_custom AS (meh text, meh2 text);
+CREATE DOMAIN d_custom AS t_custom;
+
+CREATE COLLATION custom (
+    LOCALE = 'fr-x-icu', PROVIDER = 'icu'
+);
+
+CREATE TYPE myrange AS range (subtype = text);
+CREATE TYPE myrange_en_fr_ga AS range(subtype = t_en_fr_ga);
+
+CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
+
+CREATE TABLE collate_test (
+    id integer,
+    val text COLLATE "fr-x-icu",
+    t_en_fr t_en_fr,
+    d_en_fr d_en_fr,
+    d_es d_es,
+    t_en_fr_ga t_en_fr_ga,
+    d_en_fr_ga d_en_fr_ga,
+    d_en_fr_ga_arr d_en_fr_ga[],
+    myrange myrange,
+    myrange_en_fr_ga myrange_en_fr_ga,
+    mood mood
+);
+
+CREATE INDEX icuidx00_val ON collate_test(val);
+-- shouldn't get duplicated dependencies
+CREATE INDEX icuidx00_val_val ON collate_test(val, val);
+-- shouldn't track version
+CREATE INDEX icuidx00_val_pattern ON collate_test(val text_pattern_ops);
+-- should have single dependency, no version
+CREATE INDEX icuidx00_val_pattern_val_pattern ON collate_test(val text_pattern_ops, val text_pattern_ops);
+-- should have single dependency, with version
+CREATE INDEX icuidx00_val_pattern_val ON collate_test(val text_pattern_ops, val);
+-- should have single dependency, with version
+CREATE INDEX icuidx00_val_val_pattern ON collate_test(val, val text_pattern_ops);
+-- two rows expected, only one a version, because we don't try to merge these yet
+CREATE INDEX icuidx00_val_pattern_where ON collate_test(val text_pattern_ops) WHERE val >= val;
+-- two rows expected with version, because we don't try to merge these yet
+CREATE INDEX icuidx00_val_where ON collate_test(val) WHERE val >= val;
+-- two rows expected with version (expression walker + attribute)
+CREATE INDEX icuidx00_val_pattern_expr ON collate_test(val varchar_pattern_ops, (val || val));
+-- two rows expected, one with a version (expression walker + attribute)
+CREATE INDEX icuidx00_val_pattern_expr_pattern ON collate_test(val varchar_pattern_ops, (val || val) text_pattern_ops);
+-- should have single dependency, with version tracked
+CREATE INDEX icuidx01_t_en_fr__d_es ON collate_test (t_en_fr, d_es);
+CREATE INDEX icuidx02_d_en_fr ON collate_test (d_en_fr);
+CREATE INDEX icuidx03_t_en_fr_ga ON collate_test (t_en_fr_ga);
+CREATE INDEX icuidx04_d_en_fr_ga ON collate_test (d_en_fr_ga);
+CREATE INDEX icuidx05_d_en_fr_ga_arr ON collate_test (d_en_fr_ga_arr);
+CREATE INDEX icuidx06_d_en_fr_ga ON collate_test(id) WHERE (d_en_fr_ga).en_fr.fr = 'foo';
+CREATE INDEX icuidx07_d_en_fr_ga ON collate_test(id) WHERE (d_en_fr_ga).ga = 'foo';
+CREATE INDEX icuidx08_d_en_fr_ga ON collate_test(id) WHERE (t_en_fr_ga) = ('foo', 'bar', 'baz');
+CREATE INDEX icuidx09_d_en_fr_ga ON collate_test(id) WHERE (d_en_fr_ga) = ('foo', 'bar', 'baz');
+CREATE INDEX icuidx10_d_en_fr_ga_es ON collate_test(id) WHERE (d_en_fr_ga) = ('foo', 'bar', 'baz' COLLATE "es-x-icu");
+CREATE INDEX icuidx11_d_es ON collate_test(id) WHERE (d_es) = ('foo');
+CREATE INDEX icuidx12_custom ON collate_test(id) WHERE ('foo', 'bar')::d_custom = ('foo', 'bar' COLLATE custom)::d_custom;
+CREATE INDEX icuidx13_custom ON collate_test(id) WHERE ('foo' COLLATE custom, 'bar')::d_custom = ('foo', 'bar')::d_custom;
+CREATE INDEX icuidx14_myrange ON collate_test(myrange);
+CREATE INDEX icuidx15_myrange_en_fr_ga ON collate_test USING gist (myrange_en_fr_ga);
+CREATE INDEX icuidx16_mood ON collate_test(id) WHERE mood > 'ok' COLLATE "fr-x-icu";
+
+CREATE TABLE collate_part(id integer, val text COLLATE "en-x-icu") PARTITION BY range(id);
+CREATE TABLE collate_part_0 PARTITION OF collate_part FOR VALUES FROM (0) TO (1);
+CREATE TABLE collate_part_1 PARTITION OF collate_part FOR VALUES FROM (1) TO (1000000);
+CREATE INDEX icuidx17_part ON collate_part_1 (val);
+
+SELECT objid::regclass::text collate "C", refobjid::regcollation::text collate "C",
+CASE
+WHEN refobjid = 'default'::regcollation THEN 'XXX' -- depends on libc version support
+WHEN refobjversion IS NULL THEN 'version not tracked'
+WHEN refobjversion = pg_collation_actual_version(refobjid) THEN 'up to date'
+ELSE 'out of date'
+END AS version
+FROM pg_depend d
+LEFT JOIN pg_class c ON c.oid = d.objid
+WHERE refclassid = 'pg_collation'::regclass
+AND coalesce(relkind, 'i') = 'i'
+AND relname LIKE 'icuidx%'
+ORDER BY 1, 2, 3;
+
+-- Validate that REINDEX will update the stored version.
+UPDATE pg_depend SET refobjversion = 'not a version'
+WHERE refclassid = 'pg_collation'::regclass
+AND objid::regclass::text LIKE 'icuidx%'
+AND refobjversion IS NOT NULL;
+
+REINDEX TABLE collate_test;
+REINDEX TABLE collate_part_0;
+REINDEX TABLE collate_part_1;
+
+SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version';
+
+-- Validate that REINDEX CONCURRENTLY will update the stored version.
+UPDATE pg_depend SET refobjversion = 'not a version'
+WHERE refclassid = 'pg_collation'::regclass
+AND objid::regclass::text LIKE 'icuidx%'
+AND refobjversion IS NOT NULL;
+REINDEX TABLE CONCURRENTLY collate_test;
+REINDEX TABLE CONCURRENTLY collate_part_0;
+REINDEX INDEX CONCURRENTLY icuidx17_part;
+
+SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version';
+
+-- Validate that VACUUM FULL will update the stored version.
+UPDATE pg_depend SET refobjversion = 'not a version'
+WHERE refclassid = 'pg_collation'::regclass
+AND objid::regclass::text LIKE 'icuidx%'
+AND refobjversion IS NOT NULL;
+VACUUM FULL collate_test;
+VACUUM FULL collate_part_0;
+VACUUM FULL collate_part_1;
+
+SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version';
+
+-- Test ALTER INDEX name ALTER COLLATION name REFRESH VERSION
+UPDATE pg_depend SET refobjversion = 'not a version'
+WHERE refclassid = 'pg_collation'::regclass
+AND objid::regclass::text = 'icuidx17_part'
+AND refobjversion IS NOT NULL;
+SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version';
+ALTER INDEX icuidx17_part ALTER COLLATION "en-x-icu" REFRESH VERSION;
+SELECT objid::regclass, refobjversion = 'not a version' AS ver FROM pg_depend
+WHERE refclassid = 'pg_collation'::regclass
+AND objid::regclass::text = 'icuidx17_part';
 
 -- cleanup
 RESET search_path;
index b6acade6c678e3fb2ed313282131d4eba347e087..03c4e0fe5ba507b7d1a534a6a5664dc111fbd135 100644 (file)
@@ -2914,6 +2914,8 @@ dlist_head
 dlist_iter
 dlist_mutable_iter
 dlist_node
+do_collation_version_check_context
+do_collation_version_update_context
 ds_state
 dsa_area
 dsa_area_control