Subject: [PATCH 4/6] Track collation versions for indexes.

From: Thomas Munro <[email-masked]>
Date: Tue, 28 May 2019 14:16:29 -0400

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
---
 src/backend/catalog/dependency.c              | 189 ++++++++++++++++--
 src/backend/catalog/heap.c                    |   7 +-
 src/backend/catalog/index.c                   | 139 ++++++++++++-
 src/backend/catalog/pg_constraint.c           |   2 +-
 src/backend/catalog/pg_depend.c               | 127 ++++++++++--
 src/backend/catalog/pg_type.c                 |  69 +++++++
 src/backend/commands/vacuum.c                 |  31 +++
 src/backend/optimizer/util/plancat.c          |   9 +
 src/backend/utils/adt/pg_locale.c             |  38 ++++
 src/backend/utils/cache/relcache.c            |   2 +
 src/include/catalog/dependency.h              |  21 +-
 src/include/catalog/index.h                   |   2 +
 src/include/catalog/pg_type.h                 |   2 +
 src/include/utils/pg_locale.h                 |   1 +
 src/include/utils/rel.h                       |   1 +
 .../regress/expected/collate.icu.utf8.out     | 118 +++++++++++
 ...te.icu.utf8.out => collate.icu.utf8_2.out} | 118 +++++++++++
 src/test/regress/expected/create_index.out    |   8 +-
 src/test/regress/sql/collate.icu.utf8.sql     |  76 +++++++
 19 files changed, 901 insertions(+), 59 deletions(-)
 copy src/test/regress/expected/{collate.icu.utf8.out => collate.icu.utf8_2.out} (89%)

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..cd5c93d4ef 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -74,6 +74,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"
@@ -724,6 +725,7 @@ index_create(Relation heapRelation,
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
+	List	   *collations = NIL;
 
 	/* constraint flags can only be set when a constraint is requested */
 	Assert((constr_flags == 0) ||
@@ -1115,21 +1117,44 @@ 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 */
+		/*
+		 * Get required distinct dependencies on collations for all index keys.
+		 * Collations of directly referenced column in hash indexes can be
+		 * skipped is they're deterministic.
+		 */
 		for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
 		{
-			if (OidIsValid(collationObjectId[i]) &&
-				collationObjectId[i] != DEFAULT_COLLATION_OID)
+			Oid colloid = collationObjectId[i];
+
+			if (OidIsValid(colloid))
 			{
-				referenced.classId = CollationRelationId;
-				referenced.objectId = collationObjectId[i];
-				referenced.objectSubId = 0;
+				if ((indexInfo->ii_Am != HASH_AM_OID) ||
+						!get_collation_isdeterministic(colloid))
+					collations = list_append_unique_oid(collations, colloid);
+			}
+			else
+			{
+				Form_pg_attribute att = TupleDescAttr(indexTupDesc, i);
+
+				Assert(i < indexTupDesc->natts);
 
-				recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+				collations = list_concat_unique_oid(collations,
+						GetTypeCollations(att->atttypid,
+							(indexInfo->ii_Am == HASH_AM_OID)));
 			}
 		}
 
+		if (collations)
+		{
+			recordDependencyOnCollations(&myself, collations);
+			/*
+			 * 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 +1168,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 +1262,89 @@ 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)
+	{
+		if (!version)
+		{
+			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
  *
@@ -3605,6 +3721,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..709e7f5530 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,22 +49,29 @@ 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)
 {
-	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, true);
+	}
 }
 
 /*
@@ -69,9 +81,9 @@ recordDependencyOnVersion(const ObjectAddress *depender,
 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 +91,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 +110,46 @@ recordMultipleDependencies(const ObjectAddress *depender,
 
 	for (i = 0; i < nreferenced; i++, referenced++)
 	{
+		bool ignore_systempin = false;
+
+		if (track_version)
+		{
+			/* Only dependency on a collation needs to be tracked */
+			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);
+			}
+		}
+
 		/*
 		 * 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 +606,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/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..123bfa6c01 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"
@@ -1501,6 +1503,42 @@ get_collation_actual_version(char collprovider, const char *collcollate)
 	return collversion;
 }
 
+/*
+ * Get provider-specific collation version string given a collation OID.
+ */
+char *
+get_collation_version_for_oid(Oid oid)
+{
+	HeapTuple	tp;
+	char *version;
+
+	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
 /*
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..6de17e25e8 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,28 @@ 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);
 
 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..aa06aca90a 100644
--- a/src/include/utils/pg_locale.h
+++ b/src/include/utils/pg_locale.h
@@ -104,6 +104,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/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index 60d9263a2f..f049189897 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -1897,6 +1897,124 @@ 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);
+-- version of default (and libc) collation is only tracked on glibc systems
+SELECT objid::regclass, refobjid::regcollation,
+CASE
+WHEN refobjversion IS NULL THEN 'no version'
+WHEN refobjversion = '' THEN  'unknown version'
+ELSE 'some version' 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" | some version
+ icuidx01_t_en_fr__d_es    | "es-x-icu" | some version
+ icuidx01_t_en_fr__d_es    | "fr-x-icu" | some version
+ icuidx02_d_en_fr          | "en-x-icu" | some version
+ icuidx02_d_en_fr          | "fr-x-icu" | some version
+ icuidx03_t_en_fr_ga       | "en-x-icu" | some version
+ icuidx03_t_en_fr_ga       | "fr-x-icu" | some version
+ icuidx03_t_en_fr_ga       | "ga-x-icu" | some version
+ icuidx04_d_en_fr_ga       | "en-x-icu" | some version
+ icuidx04_d_en_fr_ga       | "fr-x-icu" | some version
+ icuidx04_d_en_fr_ga       | "ga-x-icu" | some version
+ icuidx05_d_en_fr_ga_arr   | "en-x-icu" | some version
+ icuidx05_d_en_fr_ga_arr   | "fr-x-icu" | some version
+ icuidx05_d_en_fr_ga_arr   | "ga-x-icu" | some version
+ icuidx06_d_en_fr_ga       | "default"  | no version
+ icuidx06_d_en_fr_ga       | "en-x-icu" | some version
+ icuidx06_d_en_fr_ga       | "fr-x-icu" | some version
+ icuidx06_d_en_fr_ga       | "ga-x-icu" | some version
+ icuidx07_d_en_fr_ga       | "default"  | no version
+ icuidx07_d_en_fr_ga       | "en-x-icu" | some version
+ icuidx07_d_en_fr_ga       | "fr-x-icu" | some version
+ icuidx07_d_en_fr_ga       | "ga-x-icu" | some version
+ icuidx08_d_en_fr_ga       | "en-x-icu" | some version
+ icuidx08_d_en_fr_ga       | "fr-x-icu" | some version
+ icuidx08_d_en_fr_ga       | "ga-x-icu" | some version
+ icuidx09_d_en_fr_ga       | "en-x-icu" | some version
+ icuidx09_d_en_fr_ga       | "fr-x-icu" | some version
+ icuidx09_d_en_fr_ga       | "ga-x-icu" | some version
+ icuidx10_d_en_fr_ga_es    | "en-x-icu" | some version
+ icuidx10_d_en_fr_ga_es    | "es-x-icu" | some version
+ icuidx10_d_en_fr_ga_es    | "fr-x-icu" | some version
+ icuidx10_d_en_fr_ga_es    | "ga-x-icu" | some version
+ icuidx11_d_es             | "default"  | no version
+ icuidx11_d_es             | "es-x-icu" | some version
+ icuidx12_custom           | "default"  | no version
+ icuidx12_custom           | custom     | some version
+ icuidx13_custom           | "default"  | no version
+ icuidx13_custom           | custom     | some version
+ icuidx14_myrange          | "default"  | no version
+ icuidx15_myrange_en_fr_ga | "en-x-icu" | some version
+ icuidx15_myrange_en_fr_ga | "fr-x-icu" | some version
+ icuidx15_myrange_en_fr_ga | "ga-x-icu" | some version
+ icuidx16_mood             | "fr-x-icu" | some version
+ icuidx17_part             | "en-x-icu" | some version
+(44 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/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8_2.out
similarity index 89%
copy from src/test/regress/expected/collate.icu.utf8.out
copy to src/test/regress/expected/collate.icu.utf8_2.out
index 60d9263a2f..07e76c4f48 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8_2.out
@@ -1897,6 +1897,124 @@ 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);
+-- version of default (and libc) collation is only tracked on glibc systems
+SELECT objid::regclass, refobjid::regcollation,
+CASE
+WHEN refobjversion IS NULL THEN 'no version'
+WHEN refobjversion = '' THEN  'unknown version'
+ELSE 'some version' 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" | some version
+ icuidx01_t_en_fr__d_es    | "es-x-icu" | some version
+ icuidx01_t_en_fr__d_es    | "fr-x-icu" | some version
+ icuidx02_d_en_fr          | "en-x-icu" | some version
+ icuidx02_d_en_fr          | "fr-x-icu" | some version
+ icuidx03_t_en_fr_ga       | "en-x-icu" | some version
+ icuidx03_t_en_fr_ga       | "fr-x-icu" | some version
+ icuidx03_t_en_fr_ga       | "ga-x-icu" | some version
+ icuidx04_d_en_fr_ga       | "en-x-icu" | some version
+ icuidx04_d_en_fr_ga       | "fr-x-icu" | some version
+ icuidx04_d_en_fr_ga       | "ga-x-icu" | some version
+ icuidx05_d_en_fr_ga_arr   | "en-x-icu" | some version
+ icuidx05_d_en_fr_ga_arr   | "fr-x-icu" | some version
+ icuidx05_d_en_fr_ga_arr   | "ga-x-icu" | some version
+ icuidx06_d_en_fr_ga       | "default"  | some version
+ icuidx06_d_en_fr_ga       | "en-x-icu" | some version
+ icuidx06_d_en_fr_ga       | "fr-x-icu" | some version
+ icuidx06_d_en_fr_ga       | "ga-x-icu" | some version
+ icuidx07_d_en_fr_ga       | "default"  | some version
+ icuidx07_d_en_fr_ga       | "en-x-icu" | some version
+ icuidx07_d_en_fr_ga       | "fr-x-icu" | some version
+ icuidx07_d_en_fr_ga       | "ga-x-icu" | some version
+ icuidx08_d_en_fr_ga       | "en-x-icu" | some version
+ icuidx08_d_en_fr_ga       | "fr-x-icu" | some version
+ icuidx08_d_en_fr_ga       | "ga-x-icu" | some version
+ icuidx09_d_en_fr_ga       | "en-x-icu" | some version
+ icuidx09_d_en_fr_ga       | "fr-x-icu" | some version
+ icuidx09_d_en_fr_ga       | "ga-x-icu" | some version
+ icuidx10_d_en_fr_ga_es    | "en-x-icu" | some version
+ icuidx10_d_en_fr_ga_es    | "es-x-icu" | some version
+ icuidx10_d_en_fr_ga_es    | "fr-x-icu" | some version
+ icuidx10_d_en_fr_ga_es    | "ga-x-icu" | some version
+ icuidx11_d_es             | "default"  | some version
+ icuidx11_d_es             | "es-x-icu" | some version
+ icuidx12_custom           | "default"  | some version
+ icuidx12_custom           | custom     | some version
+ icuidx13_custom           | "default"  | some version
+ icuidx13_custom           | custom     | some version
+ icuidx14_myrange          | "default"  | some version
+ icuidx15_myrange_en_fr_ga | "en-x-icu" | some version
+ icuidx15_myrange_en_fr_ga | "fr-x-icu" | some version
+ icuidx15_myrange_en_fr_ga | "ga-x-icu" | some version
+ icuidx16_mood             | "fr-x-icu" | some version
+ icuidx17_part             | "en-x-icu" | some version
+(44 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..920a73512f 100644
--- a/src/test/regress/sql/collate.icu.utf8.sql
+++ b/src/test/regress/sql/collate.icu.utf8.sql
@@ -716,6 +716,82 @@ 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);
+
+-- version of default (and libc) collation is only tracked on glibc systems
+SELECT objid::regclass, refobjid::regcollation,
+CASE
+WHEN refobjversion IS NULL THEN 'no version'
+WHEN refobjversion = '' THEN  'unknown version'
+ELSE 'some version' 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.20.1

--6c2NcOVqGQ03X4Wi
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment;
	filename*0="0005-Preserve-index-dependencies-on-collation-during-pg_u-v8.";
	filename*1=patch