From 250b4f50f0c4fcb0362c68a050a6630013aa9bcb Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Tue, 28 May 2019 14:16:29 -0400
Subject: [PATCH 4/6] Track collation versions for indexes.

Record the current version of dependent collations in pg_depend when
creating or rebuilding an index.  That version is checked against current
version whenever we call get_relation_info for an index or open the parent
table during non-full VACUUM or ANALYZE. Warn that the index may be corrupted
if the versions don't match.

A new flag is added in RelationData to specify that the check has already beed
done to avoid checking and reporting the message multiple time in a backend
lifetime.

Author: Thomas Munro, Julien Rouhaud
Reviewed-by:
Discussion: https://postgr.es/m/CAEepm%3D0uEQCpfq_%2BLYFBdArCe4Ot98t1aR4eYiYTe%3DyavQygiQ%40mail.gmail.com
---
 doc/src/sgml/func.sgml                        |   2 +-
 src/backend/catalog/dependency.c              | 189 +++++++++++++++--
 src/backend/catalog/heap.c                    |   7 +-
 src/backend/catalog/index.c                   | 191 +++++++++++++++++-
 src/backend/catalog/pg_constraint.c           |   2 +-
 src/backend/catalog/pg_depend.c               | 136 +++++++++++--
 src/backend/catalog/pg_type.c                 |  69 +++++++
 src/backend/commands/collationcmds.c          |  22 +-
 src/backend/commands/vacuum.c                 |  31 +++
 src/backend/optimizer/util/plancat.c          |   9 +
 src/backend/utils/adt/pg_locale.c             |  52 ++++-
 src/backend/utils/cache/relcache.c            |   2 +
 src/include/catalog/dependency.h              |  22 +-
 src/include/catalog/index.h                   |   2 +
 src/include/catalog/pg_type.h                 |   2 +
 src/include/utils/pg_locale.h                 |   2 +-
 src/include/utils/rel.h                       |   1 +
 src/test/Makefile                             |   5 +-
 src/test/locale/.gitignore                    |   1 +
 src/test/locale/Makefile                      |   7 +
 src/test/locale/t/001_index.pl                |  76 +++++++
 .../regress/expected/collate.icu.utf8.out     | 130 ++++++++++++
 src/test/regress/expected/create_index.out    |   8 +-
 src/test/regress/sql/collate.icu.utf8.sql     |  83 ++++++++
 24 files changed, 968 insertions(+), 83 deletions(-)
 create mode 100644 src/test/locale/t/001_index.pl

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8fa131d60d..f1ef11d60d 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21104,7 +21104,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
    <para>
     <function>pg_collation_actual_version</function> returns the actual
     version of the collation object as it is currently installed in the
-    operating system.
+    operating system.  An empty string is returned if the version is unknown.
    </para>
 
    <para>
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 78c31baa34..93f57cd633 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -77,6 +77,7 @@
 #include "parser/parsetree.h"
 #include "rewrite/rewriteRemove.h"
 #include "storage/lmgr.h"
+#include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
@@ -137,6 +138,9 @@ typedef struct
 {
 	ObjectAddresses *addrs;		/* addresses being accumulated */
 	List	   *rtables;		/* list of rangetables to resolve Vars */
+	bool		track_version;	/* whether caller asked to track dependency
+								 * versions */
+	NodeTag		type;			/* nodetag of the current node */
 } find_expr_references_context;
 
 /*
@@ -437,6 +441,80 @@ performMultipleDeletions(const ObjectAddresses *objects,
 	table_close(depRel, RowExclusiveLock);
 }
 
+/*
+ * Call a function for all objects that depend on 'object'.  If the function
+ * returns a non-NULL pointer to a new version string, update the version.
+ */
+void
+visitDependentObjects(const ObjectAddress *object,
+					  VisitDependentObjectsFun 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 *cur_version, *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);
+
+		cur_version = isnull ? NULL : TextDatumGetCString(depversion);
+
+		new_version = callback(&otherObject, cur_version, userdata);
+		if (new_version)
+		{
+			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));
+
+			values[Anum_pg_depend_refobjversion - 1] =
+				CStringGetTextDatum(new_version);
+			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'
  *
@@ -1590,6 +1668,10 @@ recordDependencyOnExpr(const ObjectAddress *depender,
 	find_expr_references_context context;
 
 	context.addrs = new_object_addresses();
+	if (expr)
+		context.type = expr->type;
+	else
+		context.type = T_Invalid;
 
 	/* Set up interpretation for Vars at varlevelsup = 0 */
 	context.rtables = list_make1(rtable);
@@ -1602,9 +1684,10 @@ recordDependencyOnExpr(const ObjectAddress *depender,
 
 	/* And record 'em */
 	recordMultipleDependencies(depender,
-							   context.addrs->refs, NULL,
+							   context.addrs->refs,
 							   context.addrs->numrefs,
-							   behavior);
+							   behavior,
+							   false);
 
 	free_object_addresses(context.addrs);
 }
@@ -1631,12 +1714,18 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 								Node *expr, Oid relId,
 								DependencyType behavior,
 								DependencyType self_behavior,
-								bool reverse_self)
+								bool reverse_self,
+								bool track_version)
 {
 	find_expr_references_context context;
 	RangeTblEntry rte;
 
 	context.addrs = new_object_addresses();
+	context.track_version = track_version;
+	if (expr)
+		context.type = expr->type;
+	else
+		context.type = T_Invalid;
 
 	/* We gin up a rather bogus rangetable list to handle Vars */
 	MemSet(&rte, 0, sizeof(rte));
@@ -1690,9 +1779,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 		/* Record the self-dependencies with the appropriate direction */
 		if (!reverse_self)
 			recordMultipleDependencies(depender,
-									   self_addrs->refs, NULL,
+									   self_addrs->refs,
 									   self_addrs->numrefs,
-									   self_behavior);
+									   self_behavior,
+									   track_version);
 		else
 		{
 			/* Can't use recordMultipleDependencies, so do it the hard way */
@@ -1711,9 +1801,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 
 	/* Record the external dependencies */
 	recordMultipleDependencies(depender,
-							   context.addrs->refs, NULL,
+							   context.addrs->refs,
 							   context.addrs->numrefs,
-							   behavior);
+							   behavior,
+							   track_version);
 
 	free_object_addresses(context.addrs);
 }
@@ -1735,8 +1826,13 @@ static bool
 find_expr_references_walker(Node *node,
 							find_expr_references_context *context)
 {
+	NodeTag parent_type = context->type;
+
 	if (node == NULL)
 		return false;
+
+	context->type = node->type;
+
 	if (IsA(node, Var))
 	{
 		Var		   *var = (Var *) node;
@@ -1769,6 +1865,46 @@ 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);
+
+			/* Record collations from the type itself, or underlying in case of
+			 * complex type.  Note that if the direct parent is a CollateExpr
+			 * node, there's no need to record the type underlying collation if
+			 * any.  A dependency already exists for the owning relation, and a
+			 * change in the collation sort order wouldn't cause any harm as
+			 * the collation isn't used at all in such case.
+			 */
+			if (parent_type != T_CollateExpr)
+			{
+				/* type's collation if valid */
+				if (OidIsValid(var->varcollid))
+				{
+					add_object_address(OCLASS_COLLATION, var->varcollid, 0,
+									   context->addrs);
+				}
+				/*
+				 * otherwise, it may be a composite type having underlying
+				 * collations */
+				else if (var->vartype >= FirstNormalObjectId)
+				{
+					List	   *collations = NIL;
+					ListCell   *lc;
+
+					collations = GetTypeCollations(var->vartype, false);
+
+					foreach(lc, collations)
+					{
+						Oid coll = lfirst_oid(lc);
+
+						if (OidIsValid(coll) &&
+							(coll != DEFAULT_COLLATION_OID ||
+							 context->track_version)
+						)
+							add_object_address(OCLASS_COLLATION,
+									lfirst_oid(lc), 0,
+									context->addrs);
+					}
+				}
+			}
 		}
 
 		/*
@@ -1793,11 +1929,13 @@ 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.  However we can save work in the most common case
+		 * where the collation is "default", since we know that's pinned, if
+		 * the caller didn't ask to track dependency versions.
 		 */
 		if (OidIsValid(con->constcollid) &&
-			con->constcollid != DEFAULT_COLLATION_OID)
+				(con->constcollid != DEFAULT_COLLATION_OID ||
+					context->track_version))
 			add_object_address(OCLASS_COLLATION, con->constcollid, 0,
 							   context->addrs);
 
@@ -1887,7 +2025,8 @@ find_expr_references_walker(Node *node,
 						   context->addrs);
 		/* and its collation, just as for Consts */
 		if (OidIsValid(param->paramcollid) &&
-			param->paramcollid != DEFAULT_COLLATION_OID)
+				(param->paramcollid != DEFAULT_COLLATION_OID ||
+					context->track_version))
 			add_object_address(OCLASS_COLLATION, param->paramcollid, 0,
 							   context->addrs);
 	}
@@ -1975,7 +2114,8 @@ find_expr_references_walker(Node *node,
 							   context->addrs);
 		/* the collation might not be referenced anywhere else, either */
 		if (OidIsValid(fselect->resultcollid) &&
-			fselect->resultcollid != DEFAULT_COLLATION_OID)
+			(fselect->resultcollid != DEFAULT_COLLATION_OID ||
+			 context->track_version))
 			add_object_address(OCLASS_COLLATION, fselect->resultcollid, 0,
 							   context->addrs);
 	}
@@ -2006,7 +2146,8 @@ find_expr_references_walker(Node *node,
 						   context->addrs);
 		/* the collation might not be referenced anywhere else, either */
 		if (OidIsValid(relab->resultcollid) &&
-			relab->resultcollid != DEFAULT_COLLATION_OID)
+			(relab->resultcollid != DEFAULT_COLLATION_OID ||
+			context->track_version))
 			add_object_address(OCLASS_COLLATION, relab->resultcollid, 0,
 							   context->addrs);
 	}
@@ -2019,7 +2160,8 @@ find_expr_references_walker(Node *node,
 						   context->addrs);
 		/* the collation might not be referenced anywhere else, either */
 		if (OidIsValid(iocoerce->resultcollid) &&
-			iocoerce->resultcollid != DEFAULT_COLLATION_OID)
+			(iocoerce->resultcollid != DEFAULT_COLLATION_OID ||
+			context->track_version))
 			add_object_address(OCLASS_COLLATION, iocoerce->resultcollid, 0,
 							   context->addrs);
 	}
@@ -2032,7 +2174,8 @@ find_expr_references_walker(Node *node,
 						   context->addrs);
 		/* the collation might not be referenced anywhere else, either */
 		if (OidIsValid(acoerce->resultcollid) &&
-			acoerce->resultcollid != DEFAULT_COLLATION_OID)
+			(acoerce->resultcollid != DEFAULT_COLLATION_OID ||
+			context->track_version))
 			add_object_address(OCLASS_COLLATION, acoerce->resultcollid, 0,
 							   context->addrs);
 		/* fall through to examine arguments */
@@ -2121,7 +2264,8 @@ find_expr_references_walker(Node *node,
 			add_object_address(OCLASS_PROC, wc->endInRangeFunc, 0,
 							   context->addrs);
 		if (OidIsValid(wc->inRangeColl) &&
-			wc->inRangeColl != DEFAULT_COLLATION_OID)
+			(wc->inRangeColl != DEFAULT_COLLATION_OID ||
+			context->track_version))
 			add_object_address(OCLASS_COLLATION, wc->inRangeColl, 0,
 							   context->addrs);
 		/* fall through to examine substructure */
@@ -2266,7 +2410,9 @@ find_expr_references_walker(Node *node,
 		{
 			Oid			collid = lfirst_oid(ct);
 
-			if (OidIsValid(collid) && collid != DEFAULT_COLLATION_OID)
+			if (OidIsValid(collid) &&
+					(collid != DEFAULT_COLLATION_OID ||
+					 context->track_version))
 				add_object_address(OCLASS_COLLATION, collid, 0,
 								   context->addrs);
 		}
@@ -2288,7 +2434,9 @@ find_expr_references_walker(Node *node,
 		{
 			Oid			collid = lfirst_oid(ct);
 
-			if (OidIsValid(collid) && collid != DEFAULT_COLLATION_OID)
+			if (OidIsValid(collid) &&
+					(collid != DEFAULT_COLLATION_OID ||
+					context->track_version))
 				add_object_address(OCLASS_COLLATION, collid, 0,
 								   context->addrs);
 		}
@@ -2684,8 +2832,9 @@ record_object_address_dependencies(const ObjectAddress *depender,
 {
 	eliminate_duplicate_dependencies(referenced);
 	recordMultipleDependencies(depender,
-							   referenced->refs, NULL, referenced->numrefs,
-							   behavior);
+							   referenced->refs, referenced->numrefs,
+							   behavior,
+							   false);
 }
 
 /*
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e31478bf91..46e9d74a97 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2304,7 +2304,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
 		 */
 		recordDependencyOnSingleRelExpr(&colobject, expr, RelationGetRelid(rel),
 										DEPENDENCY_AUTO,
-										DEPENDENCY_AUTO, false);
+										DEPENDENCY_AUTO, false, false);
 	}
 	else
 	{
@@ -2314,7 +2314,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
 		 */
 		recordDependencyOnSingleRelExpr(&defobject, expr, RelationGetRelid(rel),
 										DEPENDENCY_NORMAL,
-										DEPENDENCY_NORMAL, false);
+										DEPENDENCY_NORMAL, false, false);
 	}
 
 	/*
@@ -3638,7 +3638,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
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8880586c37..c6c51e0a48 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,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"
@@ -74,6 +75,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/snapmgr.h"
@@ -117,6 +119,7 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
 								bool immediate,
 								bool isvalid,
 								bool isready);
+static bool index_depends_stable_coll_order(Oid amoid);
 static void index_update_stats(Relation rel,
 							   bool hasindex,
 							   double reltuples);
@@ -1025,6 +1028,10 @@ index_create(Relation heapRelation,
 	{
 		ObjectAddress myself,
 					referenced;
+		ListCell   *lc;
+		List	   *colls = NIL,
+				   *determ_colls = NIL,
+				   *nondeterm_colls = NIL;
 
 		myself.classId = RelationRelationId;
 		myself.objectId = indexRelationId;
@@ -1115,21 +1122,75 @@ index_create(Relation heapRelation,
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_SEC);
 		}
 
-		/* Store dependency on collations */
-		/* The default collation is pinned, so don't bother recording it */
+		/* First, get all 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))
+				colls = lappend_oid(colls, colloid);
+			else
 			{
-				referenced.classId = CollationRelationId;
-				referenced.objectId = collationObjectId[i];
-				referenced.objectSubId = 0;
+				Form_pg_attribute att = TupleDescAttr(indexTupDesc, i);
 
-				recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+				Assert(i < indexTupDesc->natts);
+
+				colls = list_concat(colls,
+									GetTypeCollations(att->atttypid, false));
 			}
 		}
 
+		/*
+		 * Then split the dependencies on whether they're deterministic or not,
+		 * removing any duplicates.
+		 */
+		foreach(lc, colls)
+		{
+			Oid c = lfirst_oid(lc);
+
+			if (!OidIsValid(c))
+				continue;
+
+			if (get_collation_isdeterministic(c))
+				determ_colls = list_append_unique_oid(determ_colls, c);
+			else
+				nondeterm_colls = list_append_unique_oid(nondeterm_colls, c);
+		}
+
+		/*
+		 * For deterministic transaction, only track the version if the AM
+		 * relies on a stable ordering.
+		 */
+		if (determ_colls)
+		{
+			bool track_version;
+
+			track_version = index_depends_stable_coll_order(indexInfo->ii_Am);
+
+			recordDependencyOnCollations(&myself, determ_colls, track_version);
+			/*
+			 * Advance the command counter so that later calls to
+			 * recordMultipleDependencies calls can see the newly-entered
+			 * pg_depend catalog tuples for the index.
+			 */
+			CommandCounterIncrement();
+		}
+
+		/*
+		 * We always record the version for dependency on non-deterministic
+		 * collations.
+		 */
+		if (nondeterm_colls)
+		{
+			recordDependencyOnCollations(&myself, nondeterm_colls, true);
+			/*
+			 * Advance the command counter so that later calls to
+			 * recordMultipleDependencies calls can see the newly-entered
+			 * pg_depend catalog tuples for the index.
+			 */
+			CommandCounterIncrement();
+		}
+
 		/* Store dependency on operator classes */
 		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
 		{
@@ -1143,21 +1204,29 @@ index_create(Relation heapRelation,
 		/* Store dependencies on anything mentioned in index expressions */
 		if (indexInfo->ii_Expressions)
 		{
+			/* recordDependencyOnSingleRelExpr get rid of duplicate entries */
 			recordDependencyOnSingleRelExpr(&myself,
 											(Node *) indexInfo->ii_Expressions,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO, false);
+											DEPENDENCY_AUTO, false, true);
+			/*
+			 * Advance the command counter so that later
+			 * recordMultipleDependencies calls can see the newly-entered
+			 * pg_depend catalog tuples for the index.
+			 */
+			CommandCounterIncrement();
 		}
 
 		/* Store dependencies on anything mentioned in predicate */
 		if (indexInfo->ii_Predicate)
 		{
+			/* recordDependencyOnSingleRelExpr get rid of duplicate entries */
 			recordDependencyOnSingleRelExpr(&myself,
 											(Node *) indexInfo->ii_Predicate,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO, false);
+											DEPENDENCY_AUTO, false, true);
 		}
 	}
 	else
@@ -1229,6 +1298,94 @@ index_create(Relation heapRelation,
 	return indexRelationId;
 }
 
+static char *
+index_check_collation_version(const ObjectAddress *otherObject,
+							  const char *version,
+							  void *userdata)
+{
+	Oid			relid = *(Oid *) userdata;
+	char	   *current_version;
+
+	/* We only care about dependencies on collations. */
+	if (otherObject->classId != CollationRelationId)
+		return NULL;
+
+	/* Compare with the current version. */
+	current_version = get_collation_version_for_oid(otherObject->objectId);
+
+	/* XXX should we warn about "disappearing" versions? */
+	if (current_version)
+	{
+		/*
+		 * We now support versioning for the underlying collation library on
+		 * this system, or previous version is unknown.
+		 */
+		if (!version || (strcmp(version, "") == 0 && strcmp(current_version,
+						"") != 0))
+		{
+			ereport(WARNING,
+					(errmsg("index \"%s\" depends on collation \"%s\" with an unknown version, and the current version is \"%s\"",
+							get_rel_name(relid),
+							get_collation_name(otherObject->objectId),
+							current_version),
+					 errdetail("The index may be corrupted due to changes in sort order."),
+					 errhint("REINDEX to avoid the risk of corruption.")));
+		}
+		else if (strcmp(current_version, version) != 0)
+		{
+			ereport(WARNING,
+				(errmsg("index \"%s\" depends on collation \"%s\" version \"%s\", but the current version is \"%s\"",
+						get_rel_name(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.")));
+		}
+	}
+
+	return NULL;
+}
+
+void
+index_check_collation_versions(Oid relid)
+{
+	ObjectAddress object;
+
+	object.classId = RelationRelationId;
+	object.objectId = relid;
+	object.objectSubId = 0;
+	visitDependentObjects(&object, &index_check_collation_version, &relid);
+}
+
+static char *
+index_update_collation_version(const ObjectAddress *otherObject,
+							   const char *version,
+							   void *userdata)
+{
+	char   *current_version = (char *) userdata;
+
+	/* We only care about dependencies on collations. */
+	if (otherObject->classId != CollationRelationId)
+		return NULL;
+
+	current_version = get_collation_version_for_oid(otherObject->objectId);
+	return current_version;
+}
+
+static void
+index_update_collation_versions(Oid relid)
+{
+	ObjectAddress object;
+	NameData	current_version;
+
+	object.classId = RelationRelationId;
+	object.objectId = relid;
+	object.objectSubId = 0;
+	visitDependentObjects(&object, &index_update_collation_version,
+						  &current_version);
+}
+
 /*
  * index_concurrently_create_copy
  *
@@ -2628,6 +2785,17 @@ FormIndexDatum(IndexInfo *indexInfo,
 		elog(ERROR, "wrong number of index expressions");
 }
 
+/*
+ * Returns whether the given index access method depend on a stable collation
+ * order.
+ */
+static bool
+index_depends_stable_coll_order(Oid amoid)
+{
+	return (amoid != HASH_AM_OID &&
+			strcmp(get_am_name(amoid), "bloom") != 0);
+}
+
 
 /*
  * index_update_stats --- update pg_class entry after CREATE INDEX or REINDEX
@@ -3605,6 +3773,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);
 }
 
 /*
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 3d2b1cc911..0eaefbd032 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -360,7 +360,7 @@ CreateConstraintEntry(const char *constraintName,
 		 */
 		recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
 										DEPENDENCY_NORMAL,
-										DEPENDENCY_NORMAL, false);
+										DEPENDENCY_NORMAL, false, true);
 	}
 
 	/* Post creation hook for new constraint */
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 7fdbdf0ae8..7e087fefc4 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -19,16 +19,21 @@
 #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"
 #include "commands/extension.h"
 #include "miscadmin.h"
+#include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
+#include "utils/pg_locale.h"
 #include "utils/rel.h"
 
 
+static bool dependencyExists(const ObjectAddress *depender,
+				   const ObjectAddress *referenced);
 static bool isObjectPinned(const ObjectAddress *object, Relation rel);
 
 
@@ -44,34 +49,47 @@ recordDependencyOn(const ObjectAddress *depender,
 				   const ObjectAddress *referenced,
 				   DependencyType behavior)
 {
-	recordMultipleDependencies(depender, referenced, NULL, 1, behavior);
+	recordMultipleDependencies(depender, referenced, 1, behavior, false);
 }
 
 /*
- * As recordDependencyOn(), but also capture a version string so that changes
- * in the referenced object can be detected.  The meaning of the version
- * string depends on the referenced object.  Currently it is used for
- * detecting changes in collation versions.
+ * Given a list of collations, record a dependency on its underlying collation
+ * version.
  */
-void
-recordDependencyOnVersion(const ObjectAddress *depender,
-						  const ObjectAddress *referenced,
-						  const NameData *version,
-						  DependencyType behavior)
+void recordDependencyOnCollations(ObjectAddress *myself,
+								  List *collations,
+								  bool record_version)
 {
-	recordMultipleDependencies(depender, referenced, version, 1, behavior);
+	ListCell   *lc;
+
+	foreach(lc, collations)
+	{
+		ObjectAddress referenced;
+
+		referenced.classId = CollationRelationId;
+		referenced.objectId = lfirst_oid(lc);
+		referenced.objectSubId = 0;
+
+		recordMultipleDependencies(myself, &referenced, 1,
+								  DEPENDENCY_NORMAL, record_version);
+	}
 }
 
 /*
  * Record multiple dependencies (of the same kind) for a single dependent
  * object.  This has a little less overhead than recording each separately.
+ *
+ * If track_version is true, then a record could be added even if the referenced
+ * dependency 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,
-						   const NameData *version,
 						   int nreferenced,
-						   DependencyType behavior)
+						   DependencyType behavior,
+						   bool track_version)
 {
 	Relation	dependDesc;
 	CatalogIndexState indstate;
@@ -79,6 +97,7 @@ recordMultipleDependencies(const ObjectAddress *depender,
 	int			i;
 	bool		nulls[Natts_pg_depend];
 	Datum		values[Natts_pg_depend];
+	char	   *version = NULL;
 
 	if (nreferenced <= 0)
 		return;					/* nothing to do */
@@ -97,12 +116,49 @@ recordMultipleDependencies(const ObjectAddress *depender,
 
 	for (i = 0; i < nreferenced; i++, referenced++)
 	{
+		bool ignore_systempin = false;
+
+		if (track_version)
+		{
+			/* Only dependency on a collation is supported. */
+			if (referenced->classId == CollationRelationId)
+			{
+				/* C and POSIX collations don't require tracking the version */
+				if (referenced->objectId == C_COLLATION_OID ||
+					referenced->objectId == POSIX_COLLATION_OID)
+					continue;
+
+				/*
+				 * We don't want to record redundant depedencies that are used
+				 * to track versions to avoid redundant warnings in case of
+				 * non-matching versions when those are checked.  Note that
+				 * callers have to take care of removing duplicated entries and
+				 * calling CommandCounterIncrement() if the dependencies are
+				 * registered in multiple calls.
+				 */
+				if (dependencyExists(depender, referenced))
+					continue;
+
+				/*
+				 * 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;
+				version = get_collation_version_for_oid(referenced->objectId);
+				Assert(version);
+			}
+		}
+		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))
 		{
 			/*
 			 * Record the Dependency.  Note we don't bother to check for
@@ -559,6 +615,54 @@ changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
 	return count;
 }
 
+/* dependencyExists()
+ *
+ * Test if a record exists for the given depender and referenceds addresses.
+ */
+static bool dependencyExists(const ObjectAddress *depender,
+				   const ObjectAddress *referenced)
+{
+	Relation	depRel;
+	ScanKeyData key[3];
+	SysScanDesc scan;
+	HeapTuple	tup;
+	bool	ret = false;
+
+	ScanKeyInit(&key[0],
+				Anum_pg_depend_classid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(depender->classId));
+	ScanKeyInit(&key[1],
+				Anum_pg_depend_objid,
+				BTEqualStrategyNumber, F_OIDEQ,
+				ObjectIdGetDatum(depender->objectId));
+	ScanKeyInit(&key[2],
+				Anum_pg_depend_objsubid,
+				BTEqualStrategyNumber, F_INT4EQ,
+				Int32GetDatum(depender->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);
+
+		if (foundDep->refclassid == referenced->classId &&
+			foundDep->refobjid == referenced->objectId &&
+			foundDep->refobjsubid == referenced->objectSubId)
+		{
+			ret = true;
+			break;
+		}
+	}
+	systable_endscan(scan);
+	table_close(depRel, RowExclusiveLock);
+
+	return ret;
+}
+
 /*
  * isObjectPinned()
  *
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 8d7572da51..6e91518694 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -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"
@@ -511,6 +512,74 @@ TypeCreate(Oid newTypeOid,
 	return address;
 }
 
+/* Get a list of all distinct collations oid that the given type depends on. */
+List *
+GetTypeCollations(Oid typeoid, bool non_deterministic_only)
+{
+	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))
+	{
+		if (!non_deterministic_only ||
+				!get_collation_isdeterministic(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))
+			{
+				if (!non_deterministic_only ||
+						!get_collation_isdeterministic(att->attcollation))
+					result = list_append_unique_oid(result, att->attcollation);
+			}
+			else
+				result = list_concat_unique_oid(result,
+						GetTypeCollations(att->atttypid,
+							non_deterministic_only));
+		}
+
+		relation_close(rel, NoLock);
+	}
+	else if (typeTup->typtype == TYPTYPE_DOMAIN)
+	{
+		Assert(OidIsValid(typeTup->typbasetype));
+
+		result = list_concat_unique_oid(result,
+				GetTypeCollations(typeTup->typbasetype,
+					non_deterministic_only));
+	}
+	else if (typeTup->typtype == TYPTYPE_RANGE)
+	{
+		Oid rangeid = get_range_subtype(typeTup->oid);
+
+		Assert(OidIsValid(rangeid));
+
+		result = list_concat_unique_oid(result,
+				GetTypeCollations(rangeid, non_deterministic_only));
+	}
+	else if (OidIsValid(typeTup->typelem))
+		result = list_concat_unique_oid(result,
+				GetTypeCollations(typeTup->typelem, non_deterministic_only));
+
+	ReleaseSysCache(tuple);
+
+	return result;
+}
+
 /*
  * GenerateTypeDependencies: build the dependencies needed for a type
  *
diff --git a/src/backend/commands/collationcmds.c b/src/backend/commands/collationcmds.c
index 493aa21a14..05709fab77 100644
--- a/src/backend/commands/collationcmds.c
+++ b/src/backend/commands/collationcmds.c
@@ -272,28 +272,12 @@ 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);
+	Assert(version);
 
-	if (version)
-		PG_RETURN_TEXT_P(cstring_to_text(version));
-	else
-		PG_RETURN_NULL();
+	PG_RETURN_TEXT_P(cstring_to_text(version));
 }
 
 
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17bf4..2b7de111cd 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,6 +31,8 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "access/xact.h"
+#include "catalog/catalog.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_inherits.h"
@@ -634,6 +636,35 @@ vacuum_open_relation(Oid relid, RangeVar *relation, int options,
 		rel_lock = false;
 	}
 
+	/*
+	 * Perform version sanity checks on the relation underlying indexes if
+	 * it's not a VACUUM FULL
+	 */
+	if (!(options & VACOPT_FULL) && onerel && !IsSystemRelation(onerel) &&
+			onerel->rd_rel->relhasindex)
+	{
+		List	   *indexoidlist;
+		ListCell   *l;
+
+		indexoidlist = RelationGetIndexList(onerel);
+		foreach(l, indexoidlist)
+		{
+			Oid			indexoid = lfirst_oid(l);
+			Relation	indexRelation;
+
+			indexRelation = index_open(indexoid, AccessShareLock);
+
+			/* Warn if any dependent collations' versions have moved. */
+			if (!indexRelation->rd_version_checked)
+			{
+				index_check_collation_versions(indexoid);
+				indexRelation->rd_version_checked = true;
+			}
+
+			index_close(indexRelation, NoLock);
+		}
+	}
+
 	/* if relation is opened, leave */
 	if (onerel)
 		return onerel;
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5ab8b..f2fc427fc8 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -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
diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index 597c1241f9..7dacceefeb 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -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"
@@ -142,6 +144,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
  *
@@ -1463,7 +1468,7 @@ pg_newlocale_from_collation(Oid collid)
  * NULL (if it doesn't support versions).  It must not return NULL for some
  * collcollate and not NULL for others.
  */
-char *
+static char *
 get_collation_actual_version(char collprovider, const char *collcollate)
 {
 	char	   *collversion = NULL;
@@ -1501,6 +1506,51 @@ get_collation_actual_version(char collprovider, const char *collcollate)
 	return collversion;
 }
 
+/*
+ * Get provider-specific collation version string given a collation OID.
+ *
+ * As this version is used in index dependency tracking, an empty string is
+ * returned when the version is unknown, to distinguish from dependencies where
+ * the version should not be tracked, represented by a NULL version.
+ */
+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);
+
+	if (!version)
+		return "";
+	else
+		return version;
+}
 
 #ifdef USE_ICU
 /*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ff70326474..bdf50ffe89 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -41,6 +41,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/pg_am.h"
@@ -5623,6 +5624,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;
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 77cf0612ed..8750bfc36f 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -156,7 +156,8 @@ extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 											Node *expr, Oid relId,
 											DependencyType behavior,
 											DependencyType self_behavior,
-											bool reverse_self);
+											bool reverse_self,
+											bool track_version);
 
 extern ObjectClass getObjectClass(const ObjectAddress *object);
 
@@ -176,22 +177,29 @@ extern void sort_object_addresses(ObjectAddresses *addrs);
 
 extern void free_object_addresses(ObjectAddresses *addrs);
 
+typedef char *(*VisitDependentObjectsFun) (const ObjectAddress *otherObject,
+											   const char *version,
+											   void *userdata);
+
+extern void visitDependentObjects(const ObjectAddress *object,
+								  VisitDependentObjectsFun callback,
+								  void *userdata);
+
 /* in pg_depend.c */
 
 extern void recordDependencyOn(const ObjectAddress *depender,
 							   const ObjectAddress *referenced,
 							   DependencyType behavior);
 
-extern void recordDependencyOnVersion(const ObjectAddress *depender,
-									  const ObjectAddress *referenced,
-									  const NameData *version,
-									  DependencyType behavior);
+extern void recordDependencyOnCollations(ObjectAddress *myself,
+										 List *collations,
+										 bool record_version);
 
 extern void recordMultipleDependencies(const ObjectAddress *depender,
 									   const ObjectAddress *referenced,
-									   const NameData *version,
 									   int nreferenced,
-									   DependencyType behavior);
+									   DependencyType behavior,
+									   bool track_version);
 
 extern void recordDependencyOnCurrentExtension(const ObjectAddress *object,
 											   bool isReplace);
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index a2890c1314..c619d02465 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -121,6 +121,8 @@ extern void FormIndexDatum(IndexInfo *indexInfo,
 						   Datum *values,
 						   bool *isnull);
 
+extern void index_check_collation_versions(Oid relid);
+
 extern void index_build(Relation heapRelation,
 						Relation indexRelation,
 						IndexInfo *indexInfo,
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index e1a5ab3df3..2bf6f868a5 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -335,6 +335,8 @@ extern void GenerateTypeDependencies(Oid typeObjectId,
 									 bool isDependentType,
 									 bool rebuild);
 
+extern List *GetTypeCollations(Oid typeObjectid, bool non_deterministic_only);
+
 extern void RenameTypeInternal(Oid typeOid, const char *newTypeName,
 							   Oid typeNamespace);
 
diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h
index 9cb7d91ddf..96da132c03 100644
--- a/src/include/utils/pg_locale.h
+++ b/src/include/utils/pg_locale.h
@@ -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);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04dd3f..3656ea94e8 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -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 being done yet? */
 
 	/*
 	 * rd_createSubid is the ID of the highest subtransaction the rel has
diff --git a/src/test/Makefile b/src/test/Makefile
index efb206aa75..4ef0d11702 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -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
@@ -37,7 +38,7 @@ endif
 # clean" etc to recurse into them.  (We must filter out those that we
 # have conditionally included into SUBDIRS above, else there will be
 # make confusion.)
-ALWAYS_SUBDIRS = $(filter-out $(SUBDIRS),examples kerberos ldap locale thread ssl)
+ALWAYS_SUBDIRS = $(filter-out $(SUBDIRS),examples kerberos ldap thread ssl)
 
 # We want to recurse to all subdirs for all standard targets, except that
 # installcheck and install should not recurse into the subdirectory "modules".
diff --git a/src/test/locale/.gitignore b/src/test/locale/.gitignore
index 620d3df425..64e1bf2a80 100644
--- a/src/test/locale/.gitignore
+++ b/src/test/locale/.gitignore
@@ -1 +1,2 @@
 /test-ctype
+/tmp_check/
diff --git a/src/test/locale/Makefile b/src/test/locale/Makefile
index 22a45b65f2..73495cf16b 100644
--- a/src/test/locale/Makefile
+++ b/src/test/locale/Makefile
@@ -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
index 0000000000..468fbb63b6
--- /dev/null
+++ b/src/test/locale/t/001_index.pl
@@ -0,0 +1,76 @@
+use strict;
+use warnings;
+
+use Config;
+use PostgresNode;
+use TestLib;
+use Test::More;
+
+if ($ENV{with_icu} eq 'yes')
+{
+	plan tests => 12;
+}
+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;
+
+$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',
+		'SET enable_seqscan = 0;'
+		. "EXPLAIN SELECT val FROM icu1 WHERE val = '0'");
+
+	is($ret, 0, 'EXPLAIN should succeed.');
+	like($out, qr/icu1_fr/, 'Index icu1_fr should be used.');
+	like($err, $err_like, $err_comm);
+}
+
+$node->safe_psql('postgres',
+	'CREATE TABLE icu1(val text);'
+	. 'INSERT INTO icu1 SELECT i::text FROM generate_series(1, 10000) i;'
+	. 'CREATE INDEX icu1_fr ON icu1 (val COLLATE "fr-x-icu");');
+$node->safe_psql('postgres', 'VACUUM ANALYZE icu1;');
+
+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.');
+
+# Simulate unknown collation version
+$node->safe_psql('postgres',
+	"UPDATE pg_depend SET refobjversion = ''"
+	. " WHERE refobjversion IS NOT NULL"
+	. " AND objid::regclass::text = 'icu1_fr';");
+
+test_index(qr/index "icu1_fr" depends on collation "fr-x-icu" with an unknown version, and the current version is/,
+	'Unknown collation version warning should be raised.');
+
+# Simulate previously unhandled collation versioning
+$node->safe_psql('postgres',
+	"UPDATE pg_depend SET refobjversion = NULL"
+	. " WHERE refobjversion IS NOT NULL"
+	. " AND objid::regclass::text = 'icu1_fr';");
+
+test_index(qr/index "icu1_fr" depends on collation "fr-x-icu" with an unknown version, and the current version is/,
+	'Unknown collation version warning should be raised.');
+
+$node->stop;
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index 60d9263a2f..09512c0f66 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -1897,6 +1897,136 @@ 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,
+    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 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);
+-- for key columns, hash indexes should record dependency on the collation but
+-- not the version
+CREATE INDEX icuidx18_hash_d_es ON collate_test USING hash (d_es);
+CREATE INDEX icuidx19_hash_id_d_es_eq ON collate_test USING hash (id) WHERE (d_es) = 'foo';
+CREATE INDEX icuidx20_hash_id_d_es_lt ON collate_test USING hash (id) WHERE (d_es) < 'foo';
+SELECT objid::regclass, refobjid::regcollation,
+CASE
+WHEN refobjversion IS NULL THEN 'version not tracked'
+ELSE CASE
+    WHEN refobjversion = pg_collation_actual_version(refobjid) THEN 'up to date'
+    ELSE 'out of date'
+    END
+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;
+           objid           |  refobjid  |       version       
+---------------------------+------------+---------------------
+ 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"  | up to date
+ 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"  | up to date
+ 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"  | up to date
+ icuidx11_d_es             | "es-x-icu" | up to date
+ icuidx12_custom           | "default"  | up to date
+ icuidx12_custom           | custom     | up to date
+ icuidx13_custom           | "default"  | up to date
+ icuidx13_custom           | custom     | up to date
+ icuidx14_myrange          | "default"  | up to date
+ 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
+ icuidx18_hash_d_es        | "es-x-icu" | version not tracked
+ icuidx19_hash_id_d_es_eq  | "default"  | up to date
+ icuidx19_hash_id_d_es_eq  | "es-x-icu" | up to date
+ icuidx20_hash_id_d_es_lt  | "default"  | up to date
+ icuidx20_hash_id_d_es_lt  | "es-x-icu" | up to date
+(49 rows)
+
+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)
+
 -- cleanup
 RESET search_path;
 SET client_min_messages TO warning;
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index 6ddf3a63c3..92dcda5bdc 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -2065,15 +2065,17 @@ 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 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
-(9 rows)
+(11 rows)
 
 REINDEX INDEX CONCURRENTLY concur_reindex_ind1;
 REINDEX TABLE CONCURRENTLY concur_reindex_tab;
@@ -2093,15 +2095,17 @@ 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 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
-(9 rows)
+(11 rows)
 
 -- Check that comments are preserved
 CREATE TABLE testcomment (i int);
diff --git a/src/test/regress/sql/collate.icu.utf8.sql b/src/test/regress/sql/collate.icu.utf8.sql
index 35acf91fbf..c8f1a620d2 100644
--- a/src/test/regress/sql/collate.icu.utf8.sql
+++ b/src/test/regress/sql/collate.icu.utf8.sql
@@ -716,6 +716,89 @@ 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,
+    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 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);
+-- for key columns, hash indexes should record dependency on the collation but
+-- not the version
+CREATE INDEX icuidx18_hash_d_es ON collate_test USING hash (d_es);
+CREATE INDEX icuidx19_hash_id_d_es_eq ON collate_test USING hash (id) WHERE (d_es) = 'foo';
+CREATE INDEX icuidx20_hash_id_d_es_lt ON collate_test USING hash (id) WHERE (d_es) < 'foo';
+
+SELECT objid::regclass, refobjid::regcollation,
+CASE
+WHEN refobjversion IS NULL THEN 'version not tracked'
+ELSE CASE
+    WHEN refobjversion = pg_collation_actual_version(refobjid) THEN 'up to date'
+    ELSE 'out of date'
+    END
+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;
+
+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';
 
 -- cleanup
 RESET search_path;
-- 
2.25.1

