public inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH v25 3/5] Track collation versions for indexes.
2+ messages / 2 participants
[nested] [flat]

* [PATCH v25 3/5] Track collation versions for indexes.
@ 2019-05-28 18:16  Thomas Munro <[email protected]>
  0 siblings, 0 replies; 2+ messages in thread

From: Thomas Munro @ 2019-05-28 18:16 UTC (permalink / raw)

Record the current version of dependent collations in pg_depend when creating
or rebuilding an index.  The version is checked against the 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 been
done to avoid repetition.

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

Author: Thomas Munro and Julien Rouhaud
Reviewed-by: Peter Eisentraut, Laurenz Albe
Discussion: https://postgr.es/m/CAEepm%3D0uEQCpfq_%2BLYFBdArCe4Ot98t1aR4eYiYTe%3DyavQygiQ%40mail.gmail.com
---
 doc/src/sgml/func.sgml                        |   3 +-
 doc/src/sgml/ref/pgupgrade.sgml               |  18 ++
 doc/src/sgml/ref/reindex.sgml                 |   6 +
 src/backend/catalog/dependency.c              | 188 +++++++++++--
 src/backend/catalog/heap.c                    |   7 +-
 src/backend/catalog/index.c                   | 265 ++++++++++++++++--
 src/backend/catalog/pg_constraint.c           |   2 +-
 src/backend/catalog/pg_depend.c               | 254 ++++++++++++++++-
 src/backend/catalog/pg_type.c                 |  69 +++++
 src/backend/commands/collationcmds.c          |  22 +-
 src/backend/commands/vacuum.c                 |  32 +++
 src/backend/optimizer/util/plancat.c          |   9 +
 src/backend/utils/adt/pg_locale.c             |  52 +++-
 src/backend/utils/adt/pg_upgrade_support.c    |  25 ++
 src/backend/utils/cache/relcache.c            |   2 +
 src/bin/pg_dump/Makefile                      |   2 +
 src/bin/pg_dump/pg_backup.h                   |   1 +
 src/bin/pg_dump/pg_dump.c                     | 186 +++++++++++-
 src/bin/pg_dump/pg_dump.h                     |   3 +
 src/bin/pg_dump/t/002_pg_dump.pl              | 248 ++++++++++++----
 src/bin/pg_upgrade/dump.c                     |   4 +-
 src/bin/pg_upgrade/option.c                   |   7 +
 src/bin/pg_upgrade/pg_upgrade.h               |   2 +
 src/include/catalog/dependency.h              |  32 ++-
 src/include/catalog/index.h                   |   7 +
 src/include/catalog/pg_proc.dat               |   4 +
 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 +++++
 src/test/perl/PostgresNode.pm                 |   6 +-
 .../regress/expected/collate.icu.utf8.out     | 157 +++++++++++
 src/test/regress/expected/create_index.out    |   8 +-
 src/test/regress/sql/collate.icu.utf8.sql     | 106 +++++++
 37 files changed, 1665 insertions(+), 156 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 69fe242761..0bb1d6c15f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25419,7 +25419,8 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
        </para>
        <para>
         Returns the actual version of the collation object as it is currently
-        installed in the operating system.
+        installed in the operating system.  An empty string is returned if the
+        version is unknown.
        </para></entry>
       </row>
 
diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml
index 6779a5bddc..9be2547d16 100644
--- a/doc/src/sgml/ref/pgupgrade.sgml
+++ b/doc/src/sgml/ref/pgupgrade.sgml
@@ -215,6 +215,24 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--collation-binary-compatible</option></term>
+      <listitem>
+       <para>
+        After upgrading from a release of <productname>PostgreSQL</productname>
+        older than 13, warnings may be reported when collation-dependent
+        indexes are first accessed.  This is because
+        <application>pg_upgrade</application> records the collation versions
+        as unknown, so <productname>PostgreSQL</productname> considers the
+        indexes to be potentially corrupted.  If you're certain that the
+        collation definitions used by your operating system or ICU haven't
+        changed since all indexes were created, you can use this flag
+        to mark all indexes as using the currently installed version.
+        Otherwise, see <xref linkend="sql-reindex"/>.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-?</option></term>
       <term><option>--help</option></term>
diff --git a/doc/src/sgml/ref/reindex.sgml b/doc/src/sgml/ref/reindex.sgml
index aac5d5be23..48726d261d 100644
--- a/doc/src/sgml/ref/reindex.sgml
+++ b/doc/src/sgml/ref/reindex.sgml
@@ -39,6 +39,12 @@ REINDEX [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] { IN
 
    <itemizedlist>
     <listitem>
+     <para>
+      The index depends on the sort order of a collation, and the definition
+      of the collation has changed.  This can cause an index to fail to
+      find a key that is present in the index.  On some systems, collation
+      versions are tracked in order to generate warnings when this happens.
+     </para>
      <para>
       An index has become corrupted, and no longer contains valid
       data. Although in theory this should never happen, in
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 070b27c692..19d6255ca7 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -76,6 +76,7 @@
 #include "rewrite/rewriteRemove.h"
 #include "storage/lmgr.h"
 #include "utils/acl.h"
+#include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
@@ -136,6 +137,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;
 
 /*
@@ -434,6 +438,81 @@ 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'
  *
@@ -1586,6 +1665,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);
@@ -1600,8 +1683,8 @@ recordDependencyOnExpr(const ObjectAddress *depender,
 	recordMultipleDependencies(depender,
 							   context.addrs->refs,
 							   context.addrs->numrefs,
-							   NULL,
-							   behavior);
+							   behavior,
+							   false);
 
 	free_object_addresses(context.addrs);
 }
@@ -1628,12 +1711,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));
@@ -1689,8 +1778,8 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 			recordMultipleDependencies(depender,
 									   self_addrs->refs,
 									   self_addrs->numrefs,
-									   NULL,
-									   self_behavior);
+									   self_behavior,
+									   track_version);
 		else
 		{
 			/* Can't use recordMultipleDependencies, so do it the hard way */
@@ -1711,8 +1800,8 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 	recordMultipleDependencies(depender,
 							   context.addrs->refs,
 							   context.addrs->numrefs,
-							   NULL,
-							   behavior);
+							   behavior,
+							   track_version);
 
 	free_object_addresses(context.addrs);
 }
@@ -1734,8 +1823,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;
@@ -1768,6 +1862,49 @@ 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,10 +1930,12 @@ 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.
+		 * 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);
 
@@ -1886,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);
 	}
@@ -1974,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);
 	}
@@ -2005,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);
 	}
@@ -2018,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);
 	}
@@ -2031,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 */
@@ -2120,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 */
@@ -2265,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);
 		}
@@ -2287,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);
 		}
@@ -2683,8 +2832,9 @@ record_object_address_dependencies(const ObjectAddress *depender,
 {
 	eliminate_duplicate_dependencies(referenced);
 	recordMultipleDependencies(depender,
-							   referenced->refs, referenced->numrefs, NULL,
-							   behavior);
+							   referenced->refs, referenced->numrefs,
+							   behavior,
+							   false);
 }
 
 /*
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index d279842d3c..93a7584a0c 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2295,7 +2295,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
 		 */
 		recordDependencyOnSingleRelExpr(&colobject, expr, RelationGetRelid(rel),
 										DEPENDENCY_AUTO,
-										DEPENDENCY_AUTO, false);
+										DEPENDENCY_AUTO, false, false);
 	}
 	else
 	{
@@ -2305,7 +2305,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
 		 */
 		recordDependencyOnSingleRelExpr(&defobject, expr, RelationGetRelid(rel),
 										DEPENDENCY_NORMAL,
-										DEPENDENCY_NORMAL, false);
+										DEPENDENCY_NORMAL, false, false);
 	}
 
 	/*
@@ -3629,7 +3629,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 fc088d3f52..e85aeb5502 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -53,6 +53,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "commands/defrem.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -75,6 +76,7 @@
 #include "utils/guc.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
+#include "utils/pg_locale.h"
 #include "utils/memutils.h"
 #include "utils/pg_rusage.h"
 #include "utils/snapmgr.h"
@@ -119,6 +121,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);
@@ -1029,6 +1032,10 @@ index_create(Relation heapRelation,
 	{
 		ObjectAddress myself,
 					referenced;
+		ListCell   *lc;
+		List	   *colls = NIL,
+				   *determ_colls = NIL,
+				   *nondeterm_colls = NIL;
 
 		ObjectAddressSet(myself, RelationRelationId, indexRelationId);
 
@@ -1107,19 +1114,77 @@ 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
 			{
-				ObjectAddressSet(referenced, CollationRelationId,
-								 collationObjectId[i]);
-				recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+				Form_pg_attribute att = TupleDescAttr(indexTupDesc, i);
+
+				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++)
 		{
@@ -1130,21 +1195,30 @@ index_create(Relation heapRelation,
 		/* Store dependencies on anything mentioned in index expressions */
 		if (indexInfo->ii_Expressions)
 		{
+			/* recordDependencyOnSingleRelExpr get rid of duplicated 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 duplicated entries */
 			recordDependencyOnSingleRelExpr(&myself,
 											(Node *) indexInfo->ii_Predicate,
 											heapRelationId,
 											DEPENDENCY_NORMAL,
-											DEPENDENCY_AUTO, false);
+											DEPENDENCY_AUTO, false, true);
 		}
 	}
 	else
@@ -1223,6 +1297,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;
+}
+
+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
  *
@@ -1674,14 +1836,9 @@ index_concurrently_swap(Oid newIndexId, Oid oldIndexId, const char *oldName)
 
 	/*
 	 * Swap all dependencies of and on the old index to the new one, and
-	 * vice-versa.  Note that a call to CommandCounterIncrement() would cause
-	 * duplicate entries in pg_depend, so this should not be done.
+	 * vice-versa.
 	 */
-	changeDependenciesOf(RelationRelationId, newIndexId, oldIndexId);
-	changeDependenciesOn(RelationRelationId, newIndexId, oldIndexId);
-
-	changeDependenciesOf(RelationRelationId, oldIndexId, newIndexId);
-	changeDependenciesOn(RelationRelationId, oldIndexId, newIndexId);
+	swapDependencies(RelationRelationId, newIndexId, oldIndexId);
 
 	/*
 	 * Copy over statistics from old to new index
@@ -2635,6 +2792,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
@@ -2988,6 +3156,68 @@ index_build(Relation heapRelation,
 	SetUserIdAndSecContext(save_userid, save_sec_context);
 }
 
+static char *
+index_force_collation_version(const ObjectAddress *otherObject,
+							  const char *version,
+							  void *userdata)
+{
+	NewCollationVersionDependency *forced_dependency;
+
+	forced_dependency = (NewCollationVersionDependency *) userdata;
+
+	/* We only care about dependencies on collations. */
+	if (otherObject->classId != CollationRelationId)
+		return NULL;
+
+	/*
+	 * We only care about dependencies on a specific collation if a valid Oid
+	 * was given.
+	 */
+	if (OidIsValid(forced_dependency->oid) &&
+		otherObject->objectId != forced_dependency->oid)
+		return NULL;
+
+	return forced_dependency->version;
+}
+
+/* index_force_collation_versions
+ *
+ * Override collation version dependencies for the given index.  If no
+ * collation identifier is specified, all dependent collation should be
+ * reset to an unknown version dependency, and no version should be provided
+ * either.
+ */
+void
+index_force_collation_versions(Oid indexid, Oid coll, char *version)
+{
+	Relation	index;
+	ObjectAddress object;
+	NewCollationVersionDependency forced_dependency;
+
+	Assert(version);
+
+	index = relation_open(indexid, AccessExclusiveLock);
+
+	if (index->rd_rel->relkind != RELKIND_INDEX)
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 (errmsg("\"%s\" is not an index", get_rel_name(indexid)))));
+
+	forced_dependency.oid = InvalidOid;
+	forced_dependency.version = version;
+
+	object.classId = RelationRelationId;
+	object.objectId = indexid;
+	object.objectSubId = 0;
+	visitDependentObjects(&object, &index_force_collation_version,
+						  &forced_dependency);
+
+	/* Invalidate the index relcache */
+	CacheInvalidateRelcache(index);
+
+	relation_close(index, NoLock);
+}
+
 /*
  * IndexCheckExclusion - verify that a new exclusion constraint is satisfied
  *
@@ -3616,6 +3846,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 fdc63e7dea..5b3c4ab637 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -346,7 +346,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 5d45e706cb..1b6c78791e 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -19,6 +19,7 @@
 #include "access/table.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
+#include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_depend.h"
 #include "catalog/pg_extension.h"
@@ -27,9 +28,18 @@
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
+#include "utils/pg_locale.h"
 #include "utils/rel.h"
 
 
+static long changeDependenciesOf(Oid classId, Oid oldObjectId,
+								 Oid newObjectId, bool preserve_version);
+static long changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
+								 Oid newRefObjectId);
+static bool dependencyExists(const ObjectAddress *depender,
+							 const ObjectAddress *referenced);
+static char *getDependencyVersion(const ObjectAddress *depender,
+								  const ObjectAddress *referenced);
 static bool isObjectPinned(const ObjectAddress *object, Relation rel);
 
 
@@ -45,19 +55,46 @@ recordDependencyOn(const ObjectAddress *depender,
 				   const ObjectAddress *referenced,
 				   DependencyType behavior)
 {
-	recordMultipleDependencies(depender, referenced, 1, NULL, behavior);
+	recordMultipleDependencies(depender, referenced, 1, behavior, false);
+}
+
+/*
+ * Given a list of collations, record a dependency on its underlying collation
+ * version.
+ */
+void
+recordDependencyOnCollations(ObjectAddress *myself,
+							 List *collations,
+							 bool record_version)
+{
+	ListCell   *lc;
+
+	foreach(lc, collations)
+	{
+		ObjectAddress referenced;
+
+		ObjectAddressSet(referenced, CollationRelationId, lfirst_oid(lc));
+
+		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,
 						   int nreferenced,
-						   const char *version,
-						   DependencyType behavior)
+						   DependencyType behavior,
+						   bool track_version)
 {
 	Relation	dependDesc;
 	CatalogIndexState indstate;
@@ -65,6 +102,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 */
@@ -83,12 +121,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
@@ -451,16 +526,33 @@ changeDependencyFor(Oid classId, Oid objectId,
 }
 
 /*
- * Adjust all dependency records to come from a different object of the same type
+ * Swap all dependencies of and on the old index to the new one, and
+ * vice-versa, while preserving any referenced version for the original owners.
+ * Note that a call to CommandCounterIncrement() would cause duplicate entries
+ * in pg_depend, so this should not be done.
+ */
+void
+swapDependencies(Oid classId, Oid firstObjectId, Oid secondObjectId)
+{
+	changeDependenciesOf(classId, firstObjectId, secondObjectId, true);
+	changeDependenciesOn(classId, firstObjectId, secondObjectId);
+
+	changeDependenciesOf(classId, secondObjectId, firstObjectId, true);
+	changeDependenciesOn(classId, secondObjectId, firstObjectId);
+}
+
+/*
+ * Adjust all dependency records to come from a different object of the same
+ * type, optionally preserving the original referenced version.
  *
  * classId/oldObjectId specify the old referencing object.
  * newObjectId is the new referencing object (must be of class classId).
  *
  * Returns the number of records updated.
  */
-long
+static long
 changeDependenciesOf(Oid classId, Oid oldObjectId,
-					 Oid newObjectId)
+					 Oid newObjectId, bool preserve_version)
 {
 	long		count = 0;
 	Relation	depRel;
@@ -485,13 +577,49 @@ changeDependenciesOf(Oid classId, Oid oldObjectId,
 	while (HeapTupleIsValid((tup = systable_getnext(scan))))
 	{
 		Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup);
+		Datum		values[Natts_pg_depend];
+		bool		nulls[Natts_pg_depend];
+		bool		replaces[Natts_pg_depend];
+		bool		isnull = true;
 
-		/* make a modifiable copy */
-		tup = heap_copytuple(tup);
-		depform = (Form_pg_depend) GETSTRUCT(tup);
+		memset(values, 0, sizeof(values));
+		memset(nulls, 0, sizeof(nulls));
+		memset(replaces, 0, sizeof(replaces));
 
-		depform->objid = newObjectId;
+		values[Anum_pg_depend_objid - 1] = newObjectId;
+		replaces[Anum_pg_depend_objid - 1] = true;
 
+		/*
+		 * We assume that a version would exist for both the old and new
+		 * object or none.
+		 */
+		if (preserve_version)
+		{
+			heap_getattr(tup, Anum_pg_depend_refobjversion,
+						 RelationGetDescr(depRel), &isnull);
+		}
+
+		if (!isnull)
+		{
+			ObjectAddress depender,
+						referenced;
+			char	   *version;
+
+			ObjectAddressSubSet(depender, depform->classid,
+								newObjectId, depform->objsubid);
+			ObjectAddressSubSet(referenced, depform->refclassid,
+								depform->refobjid, depform->refobjsubid);
+
+			version = getDependencyVersion(&depender, &referenced);
+			if (version)
+				values[Anum_pg_depend_refobjversion - 1] = CStringGetTextDatum(version);
+			else
+				nulls[Anum_pg_depend_refobjversion - 1] = true;
+			replaces[Anum_pg_depend_refobjversion - 1] = true;
+		}
+
+		tup = heap_modify_tuple(tup, RelationGetDescr(depRel), values,
+								nulls, replaces);
 		CatalogTupleUpdate(depRel, &tup->t_self, tup);
 
 		heap_freetuple(tup);
@@ -514,7 +642,7 @@ changeDependenciesOf(Oid classId, Oid oldObjectId,
  *
  * Returns the number of records updated.
  */
-long
+static long
 changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
 					 Oid newRefObjectId)
 {
@@ -594,6 +722,104 @@ changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
 	return count;
 }
 
+/* dependencyExists()
+ *
+ * Test if a record exists for the given depender and referenced 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;
+}
+
+static char *
+getDependencyVersion(const ObjectAddress *depender,
+					 const ObjectAddress *referenced)
+{
+	Relation	depRel;
+	ScanKeyData key[3];
+	SysScanDesc scan;
+	HeapTuple	tup;
+	Datum		depversion;
+	char	   *version = NULL;
+
+	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);
+		bool		isnull;
+
+		if (foundDep->refclassid == referenced->classId &&
+			foundDep->refobjid == referenced->objectId &&
+			foundDep->refobjsubid == referenced->objectSubId)
+		{
+			depversion = heap_getattr(tup, Anum_pg_depend_refobjversion,
+									  RelationGetDescr(depRel), &isnull);
+			version = isnull ? NULL : TextDatumGetCString(depversion);
+			break;
+		}
+	}
+	systable_endscan(scan);
+	table_close(depRel, RowExclusiveLock);
+
+	return version;
+}
+
 /*
  * isObjectPinned()
  *
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 79ffe317dd..a3836d315a 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"
@@ -512,6 +513,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 78eceda848..d4eccf163f 100644
--- a/src/backend/commands/collationcmds.c
+++ b/src/backend/commands/collationcmds.c
@@ -273,28 +273,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 d32de23e62..65e65abb9a 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"
@@ -632,6 +634,36 @@ 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, as VACUUM FULL will recreate the index and
+	 * update the recorded collation version.
+	 */
+	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 25545029d7..ad1383e7b3 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 2c5f5bfa37..4e116485c6 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"
@@ -148,6 +150,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
  *
@@ -1639,7 +1644,7 @@ pg_newlocale_from_collation(Oid collid)
  * Get provider-specific collation version string for the given collation from
  * the operating system/library.
  */
-char *
+static char *
 get_collation_actual_version(char collprovider, const char *collcollate)
 {
 	char	   *collversion = NULL;
@@ -1721,6 +1726,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/adt/pg_upgrade_support.c b/src/backend/utils/adt/pg_upgrade_support.c
index 18f2ee8226..07ff39f5aa 100644
--- a/src/backend/utils/adt/pg_upgrade_support.c
+++ b/src/backend/utils/adt/pg_upgrade_support.c
@@ -13,6 +13,7 @@
 
 #include "catalog/binary_upgrade.h"
 #include "catalog/heap.h"
+#include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_type.h"
 #include "commands/extension.h"
@@ -208,3 +209,27 @@ binary_upgrade_set_missing_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_VOID();
 }
+
+Datum
+binary_upgrade_set_index_coll_version(PG_FUNCTION_ARGS)
+{
+	Oid			relid;
+	Oid			coll;
+	char	   *version;
+
+	CHECK_IS_BINARY_UPGRADE;
+
+	relid = PG_GETARG_OID(0);
+
+	/* Detect if a collation is specified */
+	if (PG_ARGISNULL(1))
+		coll = InvalidOid;
+	else
+		coll = PG_GETARG_OID(1);
+
+	version = TextDatumGetCString(PG_GETARG_TEXT_PP(2));
+
+	index_force_collation_versions(relid, coll, version);
+
+	PG_RETURN_VOID();
+}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 0b9eb00d2d..62fefc4fcc 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -42,6 +42,7 @@
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/catalog.h"
+#include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/partition.h"
@@ -5926,6 +5927,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/bin/pg_dump/Makefile b/src/bin/pg_dump/Makefile
index 2532d9183a..c58ebaa681 100644
--- a/src/bin/pg_dump/Makefile
+++ b/src/bin/pg_dump/Makefile
@@ -16,6 +16,8 @@ subdir = src/bin/pg_dump
 top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
+export with_icu
+
 override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
 LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport)
 
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 8c0cedcd98..dc82b076ef 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -171,6 +171,7 @@ typedef struct _dumpOptions
 
 	int			sequence_data;	/* dump sequence data even in schema-only mode */
 	int			do_nothing;
+	int			unknown_coll_compat;
 } DumpOptions;
 
 /*
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 2f6bfede8e..f41acebe38 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -46,6 +46,7 @@
 #include "catalog/pg_attribute_d.h"
 #include "catalog/pg_cast_d.h"
 #include "catalog/pg_class_d.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_default_acl_d.h"
 #include "catalog/pg_largeobject_d.h"
 #include "catalog/pg_largeobject_metadata_d.h"
@@ -285,6 +286,8 @@ static void binary_upgrade_extension_member(PQExpBuffer upgrade_buffer,
 static const char *getAttrName(int attrnum, TableInfo *tblInfo);
 static const char *fmtCopyColumnList(const TableInfo *ti, PQExpBuffer buffer);
 static bool nonemptyReloptions(const char *reloptions);
+static void appendIndexCollationVersion(PQExpBuffer buffer, IndxInfo *indxinfo,
+										int enc, int unknown_coll_compat);
 static void appendReloptionsArrayAH(PQExpBuffer buffer, const char *reloptions,
 									const char *prefix, Archive *fout);
 static char *get_synchronized_snapshot(Archive *fout);
@@ -386,6 +389,7 @@ main(int argc, char **argv)
 		{"on-conflict-do-nothing", no_argument, &dopt.do_nothing, 1},
 		{"rows-per-insert", required_argument, NULL, 10},
 		{"include-foreign-data", required_argument, NULL, 11},
+		{"unknown-collations-binary-compatible", no_argument, &dopt.unknown_coll_compat, 1},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -713,6 +717,10 @@ main(int argc, char **argv)
 	if (archiveFormat != archDirectory && numWorkers > 1)
 		fatal("parallel backup only supported by the directory format");
 
+	/* Unknown collation versions can only be ignored in binary upgrade mode */
+	if (dopt.unknown_coll_compat && !dopt.binary_upgrade)
+		fatal("option --unknown-collations-binary-compatible only works in binary upgrade mode");
+
 	/* Open the output file */
 	fout = CreateArchive(filename, archiveFormat, compressLevel, dosync,
 						 archiveMode, setupDumpWorker);
@@ -7014,7 +7022,9 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				i_tablespace,
 				i_indreloptions,
 				i_indstatcols,
-				i_indstatvals;
+				i_indstatvals,
+				i_inddependoids,
+				i_inddependversions;
 	int			ntups;
 
 	for (i = 0; i < numTables; i++)
@@ -7050,7 +7060,62 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 		 * is not.
 		 */
 		resetPQExpBuffer(query);
-		if (fout->remoteVersion >= 110000)
+		if (fout->remoteVersion >= 130000)
+		{
+			appendPQExpBuffer(query,
+							  "SELECT t.tableoid, t.oid, "
+							  "t.relname AS indexname, "
+							  "inh.inhparent AS parentidx, "
+							  "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
+							  "i.indnkeyatts AS indnkeyatts, "
+							  "i.indnatts AS indnatts, "
+							  "i.indkey, i.indisclustered, "
+							  "i.indisreplident, "
+							  "c.contype, c.conname, "
+							  "c.condeferrable, c.condeferred, "
+							  "c.tableoid AS contableoid, "
+							  "c.oid AS conoid, "
+							  "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
+							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
+							  "t.reloptions AS indreloptions, "
+							  "(SELECT pg_catalog.array_agg(attnum ORDER BY attnum) "
+							  "  FROM pg_catalog.pg_attribute "
+							  "  WHERE attrelid = i.indexrelid AND "
+							  "    attstattarget >= 0) AS indstatcols,"
+							  "(SELECT pg_catalog.array_agg(attstattarget ORDER BY attnum) "
+							  "  FROM pg_catalog.pg_attribute "
+							  "  WHERE attrelid = i.indexrelid AND "
+							  "    attstattarget >= 0) AS indstatvals, "
+							  "(SELECT pg_catalog.array_agg(refobjid ORDER BY refobjid) "
+							  "  FROM pg_catalog.pg_depend "
+							  "  WHERE classid = " CppAsString2(RelationRelationId) " AND "
+							  "    objid = i.indexrelid AND "
+							  "    objsubid = 0 AND "
+							  "    refclassid = " CppAsString2(CollationRelationId) " AND "
+							  "    refobjversion IS NOT NULL) AS inddependoids, "
+							  "(SELECT pg_catalog.array_agg(quote_literal(refobjversion) ORDER BY refobjid) "
+							  "  FROM pg_catalog.pg_depend "
+							  "  WHERE classid = " CppAsString2(RelationRelationId) " AND "
+							  "    objid = i.indexrelid AND "
+							  "    objsubid = 0 AND "
+							  "    refclassid = " CppAsString2(CollationRelationId) " AND "
+							  "    refobjversion IS NOT NULL) AS inddependversions "
+							  "FROM pg_catalog.pg_index i "
+							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
+							  "JOIN pg_catalog.pg_class t2 ON (t2.oid = i.indrelid) "
+							  "LEFT JOIN pg_catalog.pg_constraint c "
+							  "ON (i.indrelid = c.conrelid AND "
+							  "i.indexrelid = c.conindid AND "
+							  "c.contype IN ('p','u','x')) "
+							  "LEFT JOIN pg_catalog.pg_inherits inh "
+							  "ON (inh.inhrelid = indexrelid) "
+							  "WHERE i.indrelid = '%u'::pg_catalog.oid "
+							  "AND (i.indisvalid OR t2.relkind = 'p') "
+							  "AND i.indisready "
+							  "ORDER BY indexname",
+							  tbinfo->dobj.catId.oid);
+		}
+		else if (fout->remoteVersion >= 110000)
 		{
 			appendPQExpBuffer(query,
 							  "SELECT t.tableoid, t.oid, "
@@ -7075,7 +7140,9 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT pg_catalog.array_agg(attstattarget ORDER BY attnum) "
 							  "  FROM pg_catalog.pg_attribute "
 							  "  WHERE attrelid = i.indexrelid AND "
-							  "    attstattarget >= 0) AS indstatvals "
+							  "    attstattarget >= 0) AS indstatvals, "
+							  "' ' AS inddependoids, "
+							  "' ' AS inddependversions "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "JOIN pg_catalog.pg_class t2 ON (t2.oid = i.indrelid) "
@@ -7114,7 +7181,9 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "' ' AS inddependoids, "
+							  "' ' AS inddependversions "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_constraint c "
@@ -7149,7 +7218,9 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "' ' AS inddependoids, "
+							  "' ' AS inddependversions "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_constraint c "
@@ -7180,7 +7251,9 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "t.reloptions AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "' ' AS inddependoids, "
+							  "' ' AS inddependversions "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_depend d "
@@ -7214,7 +7287,9 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 							  "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
 							  "null AS indreloptions, "
 							  "'' AS indstatcols, "
-							  "'' AS indstatvals "
+							  "'' AS indstatvals, "
+							  "' ' AS inddependoids, "
+							  "' ' AS inddependversions "
 							  "FROM pg_catalog.pg_index i "
 							  "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
 							  "LEFT JOIN pg_catalog.pg_depend d "
@@ -7254,6 +7329,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 		i_indreloptions = PQfnumber(res, "indreloptions");
 		i_indstatcols = PQfnumber(res, "indstatcols");
 		i_indstatvals = PQfnumber(res, "indstatvals");
+		i_inddependoids = PQfnumber(res, "inddependoids");
+		i_inddependversions = PQfnumber(res, "inddependversions");
 
 		tbinfo->indexes = indxinfo =
 			(IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo));
@@ -7279,6 +7356,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 			indxinfo[j].indreloptions = pg_strdup(PQgetvalue(res, j, i_indreloptions));
 			indxinfo[j].indstatcols = pg_strdup(PQgetvalue(res, j, i_indstatcols));
 			indxinfo[j].indstatvals = pg_strdup(PQgetvalue(res, j, i_indstatvals));
+			indxinfo[j].inddependoids = pg_strdup(PQgetvalue(res, j, i_inddependoids));
+			indxinfo[j].inddependversions = pg_strdup(PQgetvalue(res, j, i_inddependversions));
 			indxinfo[j].indkeys = (Oid *) pg_malloc(indxinfo[j].indnattrs * sizeof(Oid));
 			parseOidArray(PQgetvalue(res, j, i_indkey),
 						  indxinfo[j].indkeys, indxinfo[j].indnattrs);
@@ -16536,7 +16615,8 @@ dumpIndex(Archive *fout, IndxInfo *indxinfo)
 
 	/*
 	 * If there's an associated constraint, don't dump the index per se, but
-	 * do dump any comment for it.  (This is safe because dependency ordering
+	 * do dump any comment, or in binary upgrade mode dependency on a
+	 * collation version for it.  (This is safe because dependency ordering
 	 * will have ensured the constraint is emitted first.)	Note that the
 	 * emitted comment has to be shown as depending on the constraint, not the
 	 * index, in such cases.
@@ -16603,6 +16683,10 @@ dumpIndex(Archive *fout, IndxInfo *indxinfo)
 									"pg_catalog.pg_class",
 									"INDEX", qqindxname);
 
+		if (dopt->binary_upgrade)
+			appendIndexCollationVersion(q, indxinfo, fout->encoding,
+										dopt->unknown_coll_compat);
+
 		/* If the index defines identity, we need to record that. */
 		if (indxinfo->indisreplident)
 		{
@@ -16631,6 +16715,21 @@ dumpIndex(Archive *fout, IndxInfo *indxinfo)
 		if (indstatvalsarray)
 			free(indstatvalsarray);
 	}
+	else if (dopt->binary_upgrade)
+	{
+		appendIndexCollationVersion(q, indxinfo, fout->encoding,
+									dopt->unknown_coll_compat);
+
+		if (indxinfo->dobj.dump & DUMP_COMPONENT_DEFINITION)
+			ArchiveEntry(fout, indxinfo->dobj.catId, indxinfo->dobj.dumpId,
+						 ARCHIVE_OPTS(.tag = indxinfo->dobj.name,
+									  .namespace = tbinfo->dobj.namespace->dobj.name,
+									  .tablespace = indxinfo->tablespace,
+									  .owner = tbinfo->rolname,
+									  .description = "INDEX",
+									  .section = SECTION_POST_DATA,
+									  .createStmt = q->data));
+	}
 
 	/* Dump Index Comments */
 	if (indxinfo->dobj.dump & DUMP_COMPONENT_COMMENT)
@@ -18612,6 +18711,77 @@ nonemptyReloptions(const char *reloptions)
 	return (reloptions != NULL && strlen(reloptions) > 2);
 }
 
+/*
+ * Format inddependoids and inddependversions arrays and append it to the given
+ * buffer in the form of binary_upgrade_set_index_coll_version() calls.
+ */
+static void
+appendIndexCollationVersion(PQExpBuffer buffer, IndxInfo *indxinfo, int enc,
+							int unknown_coll_compat)
+{
+	char	   *inddependoids = indxinfo->inddependoids;
+	char	   *inddependversions = indxinfo->inddependversions;
+	char	  **inddependoidsarray = NULL;
+	char	  **inddependversionsarray = NULL;
+	int			ninddependoids;
+	int			ninddependversions;
+	int			i;
+
+	/*
+	 * for older versions that don't record the collation depndency, issue a
+	 * statement to mark the collation version as unknown
+	 */
+	if (strcmp(inddependoids, " ") == 0)
+	{
+		/*
+		 * do not issue UNKNOWN VERSION if caller specified that those are
+		 * compatible
+		 */
+		if (unknown_coll_compat)
+			return;
+
+		Assert(strcmp(inddependversions, " ") == 0);
+
+		appendPQExpBufferStr(buffer, "\n-- For binary upgrade, restore dependent collation version.\n");
+		appendPQExpBuffer(buffer, "SELECT "
+						  "pg_catalog.binary_upgrade_set_index_coll_version(%d, NULL, '');",
+						  indxinfo->dobj.catId.oid);
+		return;
+	}
+
+	parsePGArray(inddependoids, &inddependoidsarray, &ninddependoids);
+	parsePGArray(inddependversions, &inddependversionsarray, &ninddependversions);
+
+	Assert(ninddependoids == ninddependversions);
+
+	for (i = 0; i < ninddependoids; i++)
+	{
+		/*
+		 * If there was an unknown version dependency recorded for this
+		 * collation and the caller asked to mark those as depending ono
+		 * current version, don't emit a binary_upgrade_set_index_coll_version
+		 * function call.
+		 */
+		if ((strcmp(inddependversionsarray[i], "''")) == 0
+			&& unknown_coll_compat)
+		{
+			continue;
+		}
+
+		appendPQExpBufferStr(buffer, "\n-- For binary upgrade, restore dependent collation version.\n");
+		appendPQExpBuffer(buffer, "SELECT "
+						  "pg_catalog.binary_upgrade_set_index_coll_version(%d, %s, %s);",
+						  indxinfo->dobj.catId.oid,
+						  inddependoidsarray[i],
+						  inddependversionsarray[i]);
+	}
+
+	if (inddependoidsarray)
+		free(inddependoidsarray);
+	if (inddependversionsarray)
+		free(inddependversionsarray);
+}
+
 /*
  * Format a reloptions array and append it to the given buffer.
  *
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 0c2fcfb3a9..2faab3b659 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -366,6 +366,9 @@ typedef struct _indxInfo
 	int			indnattrs;		/* total number of index attributes */
 	Oid		   *indkeys;		/* In spite of the name 'indkeys' this field
 								 * contains both key and nonkey attributes */
+	char	   *inddependoids;	/* oids of collation this index depends on */
+	char	   *inddependversions;	/* version of collation this index depends
+									 * on */
 	bool		indisclustered;
 	bool		indisreplident;
 	Oid			parentidx;		/* if partitioned, parent index OID */
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index e116235769..886d123e25 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -53,6 +53,24 @@ my %pgdump_runs = (
 			"$tempdir/binary_upgrade.dump",
 		],
 	},
+	binary_coll_compatible => {
+		dump_cmd => [
+			'pg_dump',
+			'--no-sync',
+			'--format=custom',
+			"--file=$tempdir/binary_coll_compatible.dump",
+			'-w',
+			'--schema-only',
+			'--binary-upgrade',
+			'--unknown-collations-binary-compatible',
+			'-d', 'postgres',    # alternative way to specify database
+		],
+		restore_cmd => [
+			'pg_restore', '-Fc', '--verbose',
+			"--file=$tempdir/binary_coll_compatible.sql",
+			"$tempdir/binary_coll_compatible.dump",
+		],
+	},
 	clean => {
 		dump_cmd => [
 			'pg_dump',
@@ -387,6 +405,7 @@ my %dump_test_schema_runs = (
 # are flags used to exclude specific items (ACLs, blobs, etc).
 my %full_runs = (
 	binary_upgrade           => 1,
+	binary_coll_compatible   => 1,
 	clean                    => 1,
 	clean_if_exists          => 1,
 	createdb                 => 1,
@@ -916,9 +935,10 @@ my %tests = (
 			test_schema_plus_blobs => 1,
 		},
 		unlike => {
-			binary_upgrade => 1,
-			no_blobs       => 1,
-			schema_only    => 1,
+			binary_upgrade         => 1,
+			binary_coll_compatible => 1,
+			no_blobs               => 1,
+			schema_only            => 1,
 		},
 	},
 
@@ -1180,6 +1200,7 @@ my %tests = (
 		},
 		unlike => {
 			binary_upgrade           => 1,
+			binary_coll_compatible   => 1,
 			exclude_dump_test_schema => 1,
 			exclude_test_table       => 1,
 			exclude_test_table_data  => 1,
@@ -1205,6 +1226,7 @@ my %tests = (
 		},
 		unlike => {
 			binary_upgrade           => 1,
+			binary_coll_compatible   => 1,
 			exclude_dump_test_schema => 1,
 			schema_only              => 1,
 		},
@@ -1240,6 +1262,7 @@ my %tests = (
 		},
 		unlike => {
 			binary_upgrade           => 1,
+			binary_coll_compatible   => 1,
 			exclude_dump_test_schema => 1,
 			schema_only              => 1,
 		},
@@ -1262,6 +1285,7 @@ my %tests = (
 		},
 		unlike => {
 			binary_upgrade           => 1,
+			binary_coll_compatible   => 1,
 			exclude_dump_test_schema => 1,
 			schema_only              => 1,
 		},
@@ -1283,6 +1307,7 @@ my %tests = (
 		},
 		unlike => {
 			binary_upgrade           => 1,
+			binary_coll_compatible   => 1,
 			exclude_dump_test_schema => 1,
 			schema_only              => 1,
 		},
@@ -1304,6 +1329,7 @@ my %tests = (
 		},
 		unlike => {
 			binary_upgrade           => 1,
+			binary_coll_compatible   => 1,
 			exclude_dump_test_schema => 1,
 			schema_only              => 1,
 		},
@@ -1670,6 +1696,7 @@ my %tests = (
 		  { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
 		unlike => {
 			binary_upgrade           => 1,
+			binary_coll_compatible   => 1,
 			exclude_dump_test_schema => 1,
 		},
 	},
@@ -1684,7 +1711,7 @@ my %tests = (
 			\n.*^
 			\QALTER TYPE dump_test.planets ADD VALUE 'mars';\E
 			\n/xms,
-		like => { binary_upgrade => 1, },
+		like => { binary_upgrade => 1, binary_coll_compatible => 1, },
 	},
 
 	'CREATE TYPE dump_test.textrange AS RANGE' => {
@@ -2352,6 +2379,7 @@ my %tests = (
 		  { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
 		unlike => {
 			binary_upgrade           => 1,
+			binary_coll_compatible   => 1,
 			exclude_dump_test_schema => 1,
 		},
 	},
@@ -2545,6 +2573,7 @@ my %tests = (
 		},
 		unlike => {
 			binary_upgrade           => 1,
+			binary_coll_compatible   => 1,
 			exclude_dump_test_schema => 1,
 		},
 	},
@@ -2613,6 +2642,7 @@ my %tests = (
 		/xm,
 		like => {
 			binary_upgrade          => 1,
+			binary_coll_compatible  => 1,
 			clean                   => 1,
 			clean_if_exists         => 1,
 			createdb                => 1,
@@ -2684,6 +2714,7 @@ my %tests = (
 		/xm,
 		like => {
 			binary_upgrade           => 1,
+			binary_coll_compatible   => 1,
 			clean                    => 1,
 			clean_if_exists          => 1,
 			createdb                 => 1,
@@ -3151,6 +3182,7 @@ my %tests = (
 		  { %full_runs, %dump_test_schema_runs, section_post_data => 1, },
 		unlike => {
 			binary_upgrade           => 1,
+			binary_coll_compatible   => 1,
 			exclude_dump_test_schema => 1,
 			schema_only              => 1,
 		},
@@ -3166,6 +3198,7 @@ my %tests = (
 		  { %full_runs, %dump_test_schema_runs, section_post_data => 1, },
 		unlike => {
 			binary_upgrade           => 1,
+			binary_coll_compatible   => 1,
 			exclude_dump_test_schema => 1,
 			schema_only              => 1,
 		},
@@ -3298,16 +3331,53 @@ my %tests = (
 			%full_runs, %dump_test_schema_runs, section_pre_data => 1,
 		},
 		unlike => { exclude_dump_test_schema => 1 },
+	},
+
+	"binary_upgrade_set_index_coll_version(oid, oid, 'not_a_version')" => {
+		create_order => 101,
+		create_sql => '
+		CREATE TABLE dump_test.regress_table_coll(id integer, val text);
+		CREATE INDEX regress_coll_idx1 ON dump_test.regress_table_coll(val COLLATE "fr-x-icu");
+		UPDATE pg_depend SET refobjversion = \'not_a_version\' WHERE refobjversion IS NOT NULL AND objid::regclass::text = \'dump_test.regress_coll_idx1\';',
+		regexp => qr/^
+		\QCREATE INDEX regress_coll_idx1 ON dump_test.regress_table_coll USING btree (val COLLATE "fr-x-icu");\E\n
+		\n
+		\Q-- For binary upgrade, restore dependent collation version.\E\n
+		\QSELECT pg_catalog.binary_upgrade_set_index_coll_version\E \(\d+,\ \d+,\ 'not_a_version'\);/xm,
+		like => { binary_upgrade => 1, binary_coll_compatible => 1, },
+		icu => 1,
+	},
+	"binary_upgrade_set_index_coll_version(?, ?, '')" => {
+		create_order => 102,
+		create_sql => '
+		CREATE TABLE dump_test.regress_table_coll_no_ver(id integer, val text);
+		CREATE INDEX regress_coll_no_ver_idx1 ON dump_test.regress_table_coll_no_ver(val COLLATE "fr-x-icu");
+		UPDATE pg_depend SET refobjversion = \'\' WHERE refobjversion IS NOT NULL AND objid::regclass::text = \'dump_test.regress_coll_no_ver_idx1\';',
+		regexp => qr/SELECT pg_catalog.binary_upgrade_set_index_coll_version\(\d+, \d+, ''\)/,
+		like => { binary_upgrade => 1},
+		# should not appear in binary_coll_compatible case!
+		unlike => { binary_coll_compatible => 1},
+		icu => 1,
 	});
 
 #########################################
 # Create a PG instance to test actually dumping from
 
-my $node = get_new_node('main');
-$node->init;
-$node->start;
+my $main_node = get_new_node('main');
+$main_node->init;
+$main_node->start;
+
+my $port = $main_node->port;
+
+# And another instance to validate the binary dump
+my $bin_node = get_new_node('binary');
+$bin_node->init;
+$bin_node->start;
 
-my $port = $node->port;
+my $bin_port = $bin_node->port;
+
+# and add a $node variable pointing to main_node for now
+my $node = $main_node;
 
 # We need to see if this system supports CREATE COLLATION or not
 # If it doesn't then we will skip all the COLLATION-related tests.
@@ -3331,6 +3401,10 @@ $node->psql('postgres', 'create database regress_pg_dump_test;');
 # command_fails_like is actually 2 tests)
 my $num_tests = 12;
 
+# 4 more tests for restoring globals and binary_upgrade dump, dumping it again
+# and regenerating the sql file
+$num_tests+= 4;
+
 foreach my $run (sort keys %pgdump_runs)
 {
 	my $test_key = $run;
@@ -3381,16 +3455,29 @@ foreach my $run (sort keys %pgdump_runs)
 			next;
 		}
 
+		# Skip any icu-related commands if there is no icu support
+		if ($ENV{with_icu} ne 'yes' && defined($tests{$test}->{icu}))
+		{
+			next;
+		}
+
 		# If there is a like entry, but no unlike entry, then we will test the like case
 		if ($tests{$test}->{like}->{$test_key}
 			&& !defined($tests{$test}->{unlike}->{$test_key}))
 		{
 			$num_tests++;
+
+			# binary_upgrade tests are also run after being restored and
+			# re-dumped.
+			$num_tests++ if ($test_key eq 'binary_upgrade');
 		}
 		else
 		{
 			# We will test everything that isn't a 'like'
 			$num_tests++;
+			# binary_upgrade tests are also run after being restored and
+			# re-dumped.
+			$num_tests++ if ($test_key eq 'binary_upgrade');
 		}
 	}
 }
@@ -3438,6 +3525,12 @@ foreach my $test (
 			next;
 		}
 
+		# Skip any icu-related commands if there is no icu support
+		if ($ENV{with_icu} ne 'yes' && defined($tests{$test}->{icu}))
+		{
+			next;
+		}
+
 		# Add terminating semicolon
 		$create_sql{$test_db} .= $tests{$test}->{create_sql} . ";";
 	}
@@ -3491,79 +3584,116 @@ command_fails_like(
 #########################################
 # Run all runs
 
-foreach my $run (sort keys %pgdump_runs)
+foreach my $pass (1, 2)
 {
-	my $test_key = $run;
-	my $run_db   = 'postgres';
-
-	$node->command_ok(\@{ $pgdump_runs{$run}->{dump_cmd} },
-		"$run: pg_dump runs");
-
-	if ($pgdump_runs{$run}->{restore_cmd})
+	foreach my $run (sort keys %pgdump_runs)
 	{
-		$node->command_ok(\@{ $pgdump_runs{$run}->{restore_cmd} },
-			"$run: pg_restore runs");
-	}
-
-	if ($pgdump_runs{$run}->{test_key})
-	{
-		$test_key = $pgdump_runs{$run}->{test_key};
-	}
-
-	my $output_file = slurp_file("$tempdir/${run}.sql");
+		my $test_key = $run;
+		my $run_db   = 'postgres';
 
-	#########################################
-	# Run all tests where this run is included
-	# as either a 'like' or 'unlike' test.
+		# we only test binary upgrade on the 2nd pass
+		next if ($pass == 2 and $test_key ne 'binary_upgrade');
 
-	foreach my $test (sort keys %tests)
-	{
-		my $test_db = 'postgres';
+		$node->command_ok(\@{ $pgdump_runs{$run}->{dump_cmd} },
+			"$run: pg_dump runs");
 
-		if (defined($pgdump_runs{$run}->{database}))
+		if ($pgdump_runs{$run}->{restore_cmd})
 		{
-			$run_db = $pgdump_runs{$run}->{database};
+			$node->command_ok(\@{ $pgdump_runs{$run}->{restore_cmd} },
+				"$run: pg_restore runs");
 		}
 
-		if (defined($tests{$test}->{database}))
+		if ($pgdump_runs{$run}->{test_key})
 		{
-			$test_db = $tests{$test}->{database};
+			$test_key = $pgdump_runs{$run}->{test_key};
 		}
 
-		# Skip any collation-related commands if there is no collation support
-		if (!$collation_support && defined($tests{$test}->{collation}))
-		{
-			next;
-		}
+		my $output_file = slurp_file("$tempdir/${run}.sql");
 
-		if ($run_db ne $test_db)
-		{
-			next;
-		}
+		#########################################
+		# Run all tests where this run is included
+		# as either a 'like' or 'unlike' test.
 
-		# Run the test listed as a like, unless it is specifically noted
-		# as an unlike (generally due to an explicit exclusion or similar).
-		if ($tests{$test}->{like}->{$test_key}
-			&& !defined($tests{$test}->{unlike}->{$test_key}))
+		foreach my $test (sort keys %tests)
 		{
-			if (!ok($output_file =~ $tests{$test}->{regexp},
-					"$run: should dump $test"))
+			my $test_db = 'postgres';
+
+			if (defined($pgdump_runs{$run}->{database}))
 			{
-				diag("Review $run results in $tempdir");
+				$run_db = $pgdump_runs{$run}->{database};
 			}
-		}
-		else
-		{
-			if (!ok($output_file !~ $tests{$test}->{regexp},
-					"$run: should not dump $test"))
+
+			if (defined($tests{$test}->{database}))
+			{
+				$test_db = $tests{$test}->{database};
+			}
+
+			# Skip any collation-related commands if there is no collation support
+			if (!$collation_support && defined($tests{$test}->{collation}))
+			{
+				next;
+			}
+
+			# Skip any icu-related commands if there is no icu support
+	        if ($ENV{with_icu} ne 'yes' && defined($tests{$test}->{icu}))
+			{
+				next;
+			}
+
+			if ($run_db ne $test_db)
+			{
+				next;
+			}
+
+			# Run the test listed as a like, unless it is specifically noted
+			# as an unlike (generally due to an explicit exclusion or similar).
+			if ($tests{$test}->{like}->{$test_key}
+				&& !defined($tests{$test}->{unlike}->{$test_key}))
 			{
-				diag("Review $run results in $tempdir");
+				if (!ok($output_file =~ $tests{$test}->{regexp},
+						"$run: should dump $test"))
+				{
+					diag("Review $run results in $tempdir");
+				}
+			}
+			else
+			{
+				if (!ok($output_file !~ $tests{$test}->{regexp},
+						"$run: should not dump $test"))
+				{
+					diag("Review $run results in $tempdir");
+				}
 			}
 		}
 	}
+
+	# After all dump have been generated, restore the binary_upgrade dump with
+	# the required global objects on a suitable node, and continue with the 2nd
+	# pass.
+	if ($pass == 1)
+	{
+		# Stop the original database instance as we don't need it anymore.
+		$node->stop('fast');
+
+		$bin_node->command_ok(\@{['psql',
+			"-d", "postgres", "-f", "$tempdir/pg_dumpall_globals.sql"]},
+			"Restore globals");
+
+		$bin_node->stop('fast');
+		$bin_node->start(binary_start => 1);
+		$bin_node->command_ok(\@{['pg_restore', '-p', $bin_port,
+			'-d', 'postgres',
+			"$tempdir/binary_upgrade.dump"]},
+			"Restore the binary_upgrade dump");
+		$bin_node->stop('fast');
+		$bin_node->start;
+
+		# And change $node to point to the freshly restored node.
+		$node = $bin_node;
+	}
 }
 
 #########################################
 # Stop the database instance, which will be removed at the end of the tests.
 
-$node->stop('fast');
+$bin_node->stop('fast');
diff --git a/src/bin/pg_upgrade/dump.c b/src/bin/pg_upgrade/dump.c
index 4d730adfe2..672ecda169 100644
--- a/src/bin/pg_upgrade/dump.c
+++ b/src/bin/pg_upgrade/dump.c
@@ -52,9 +52,11 @@ generate_old_dump(void)
 
 		parallel_exec_prog(log_file_name, NULL,
 						   "\"%s/pg_dump\" %s --schema-only --quote-all-identifiers "
-						   "--binary-upgrade --format=custom %s --file=\"%s\" %s",
+						   "--binary-upgrade --format=custom %s %s --file=\"%s\" %s",
 						   new_cluster.bindir, cluster_conn_opts(&old_cluster),
 						   log_opts.verbose ? "--verbose" : "",
+						   user_opts.coll_compat ?
+						   "--unknown-collations-binary-compatible" : "",
 						   sql_file_name, escaped_connstr.data);
 
 		termPQExpBuffer(&escaped_connstr);
diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c
index aca1ee8b48..c7e291f7e7 100644
--- a/src/bin/pg_upgrade/option.c
+++ b/src/bin/pg_upgrade/option.c
@@ -56,6 +56,7 @@ parseCommandLine(int argc, char *argv[])
 		{"socketdir", required_argument, NULL, 's'},
 		{"verbose", no_argument, NULL, 'v'},
 		{"clone", no_argument, NULL, 1},
+		{"collation-binary-compatible", no_argument, NULL, 2},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -203,6 +204,10 @@ parseCommandLine(int argc, char *argv[])
 				user_opts.transfer_mode = TRANSFER_MODE_CLONE;
 				break;
 
+			case 2:
+				user_opts.coll_compat = true;
+				break;
+
 			default:
 				fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
 						os_info.progname);
@@ -307,6 +312,8 @@ usage(void)
 	printf(_("  -v, --verbose                 enable verbose internal logging\n"));
 	printf(_("  -V, --version                 display version information, then exit\n"));
 	printf(_("  --clone                       clone instead of copying files to new cluster\n"));
+	printf(_("  --collation-binary-compatible mark collations as depending on current collation\n"
+			 "                                versions rather than unknown if they're unknown\n"));
 	printf(_("  -?, --help                    show this help, then exit\n"));
 	printf(_("\n"
 			 "Before running pg_upgrade you must:\n"
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cefbe0..68e637ce8a 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -292,6 +292,8 @@ typedef struct
 	transferMode transfer_mode; /* copy files or link them? */
 	int			jobs;			/* number of processes/threads to use */
 	char	   *socketdir;		/* directory to use for Unix sockets */
+	bool		coll_compat;	/* should we skip marking index collations as
+								 * unknown version */
 } UserOpts;
 
 typedef struct
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 3baa5e498a..4949044041 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -130,6 +130,13 @@ typedef enum ObjectClass
 
 #define LAST_OCLASS		OCLASS_TRANSFORM
 
+/* Struct describing one forced collation version dependency */
+typedef struct NewCollationVersionDependency
+{
+	char	   *version;		/* forced collation version */
+	Oid			oid;			/* target collation oid */
+} NewCollationVersionDependency;
+
 /* flag bits for performDeletion/performMultipleDeletions: */
 #define PERFORM_DELETION_INTERNAL			0x0001	/* internal action */
 #define PERFORM_DELETION_CONCURRENTLY		0x0002	/* concurrent drop */
@@ -160,7 +167,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);
 
@@ -180,17 +188,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 recordDependencyOnCollations(ObjectAddress *myself,
+										 List *collations,
+										 bool record_version);
+
 extern void recordMultipleDependencies(const ObjectAddress *depender,
 									   const ObjectAddress *referenced,
 									   int nreferenced,
-									   const char *version,
-									   DependencyType behavior);
+									   DependencyType behavior,
+									   bool track_version);
 
 extern void recordDependencyOnCurrentExtension(const ObjectAddress *object,
 											   bool isReplace);
@@ -209,11 +229,7 @@ extern long changeDependencyFor(Oid classId, Oid objectId,
 								Oid refClassId, Oid oldRefObjectId,
 								Oid newRefObjectId);
 
-extern long changeDependenciesOf(Oid classId, Oid oldObjectId,
-								 Oid newObjectId);
-
-extern long changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
-								 Oid newRefObjectId);
+extern void swapDependencies(Oid classId, Oid firstObjectId, Oid secondObjectId);
 
 extern Oid	getExtensionOfObject(Oid classId, Oid objectId);
 extern List *getAutoExtensionsOfObject(Oid classId, Oid objectId);
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index f58e8675f3..9b4de26514 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -121,6 +121,11 @@ extern void FormIndexDatum(IndexInfo *indexInfo,
 						   Datum *values,
 						   bool *isnull);
 
+extern void index_check_collation_versions(Oid relid);
+
+extern void index_force_collation_versions(Oid indexid, Oid coll,
+										   char *version);
+
 extern void index_build(Relation heapRelation,
 						Relation indexRelation,
 						IndexInfo *indexInfo,
@@ -131,6 +136,8 @@ extern void validate_index(Oid heapId, Oid indexId, Snapshot snapshot);
 
 extern void index_set_state_flags(Oid indexId, IndexStateFlagsAction action);
 
+extern void index_update_collation_versions(Oid relid);
+
 extern Oid	IndexGetRelation(Oid indexId, bool missing_ok);
 
 extern void reindex_index(Oid indexId, bool skip_constraint_checks,
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 38295aca48..3a9bb0d783 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10343,6 +10343,10 @@
   proname => 'binary_upgrade_set_missing_value', provolatile => 'v',
   proparallel => 'u', prorettype => 'void', proargtypes => 'oid text text',
   prosrc => 'binary_upgrade_set_missing_value' },
+{ oid => '8178', descr => 'for use by pg_upgrade',
+  proname => 'binary_upgrade_set_index_coll_version', provolatile => 'v',
+  proparallel => 'u', prorettype => 'void', proargtypes => 'oid oid text',
+  prosrc => 'binary_upgrade_set_index_coll_version' },
 
 # conversion functions
 { oid => '4302',
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index 7b37562648..2d511c5cba 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -361,6 +361,8 @@ extern void GenerateTypeDependencies(HeapTuple typeTuple,
 									 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 0b5957ba02..f79667d651 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/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 1407359aef..f39b9433a8 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -765,10 +765,14 @@ sub start
 		local %ENV = %ENV;
 		delete $ENV{PGAPPNAME};
 
+		my $options = "--cluster-name=$name";
+
+		$options .= ' -b' if ($params{binary_start});
+
 		# Note: We set the cluster_name here, not in postgresql.conf (in
 		# sub init) so that it does not get copied to standbys.
 		$ret = TestLib::system_log('pg_ctl', '-D', $self->data_dir, '-l',
-			$self->logfile, '-o', "--cluster-name=$name", 'start');
+			$self->logfile, '-o', $options, 'start');
 	}
 
 	if ($ret != 0)
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index 60d9263a2f..db386c1b09 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -1897,6 +1897,163 @@ 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)
+
+-- Validate that REINDEX will update the stored version.
+UPDATE pg_depend SET refobjversion = 'not a version'
+WHERE refclassid = 'pg_collation'::regclass
+AND objid::regclass::text LIKE 'icuidx%'
+AND refobjversion IS NOT NULL;
+REINDEX TABLE collate_test;
+REINDEX TABLE collate_part_0;
+REINDEX TABLE collate_part_1;
+SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version';
+ objid 
+-------
+(0 rows)
+
+-- Validate that REINDEX CONCURRENTLY will update the stored version.
+UPDATE pg_depend SET refobjversion = 'not a version'
+WHERE refclassid = 'pg_collation'::regclass
+AND objid::regclass::text LIKE 'icuidx%'
+AND refobjversion IS NOT NULL;
+REINDEX TABLE CONCURRENTLY collate_test;
+REINDEX TABLE CONCURRENTLY collate_part_0;
+REINDEX INDEX CONCURRENTLY icuidx17_part;
+SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version';
+ objid 
+-------
+(0 rows)
+
+-- Validate that VACUUM FULL will update the stored version.
+UPDATE pg_depend SET refobjversion = 'not a version'
+WHERE refclassid = 'pg_collation'::regclass
+AND objid::regclass::text LIKE 'icuidx%'
+AND refobjversion IS NOT NULL;
+VACUUM FULL collate_test;
+VACUUM FULL collate_part_0;
+VACUUM FULL collate_part_1;
+SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version';
+ objid 
+-------
+(0 rows)
+
 -- 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 e3e6634d7e..ce3d45cf2c 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..e93530af55 100644
--- a/src/test/regress/sql/collate.icu.utf8.sql
+++ b/src/test/regress/sql/collate.icu.utf8.sql
@@ -716,6 +716,112 @@ 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;
+
+-- Validate that REINDEX will update the stored version.
+UPDATE pg_depend SET refobjversion = 'not a version'
+WHERE refclassid = 'pg_collation'::regclass
+AND objid::regclass::text LIKE 'icuidx%'
+AND refobjversion IS NOT NULL;
+
+REINDEX TABLE collate_test;
+REINDEX TABLE collate_part_0;
+REINDEX TABLE collate_part_1;
+
+SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version';
+
+-- Validate that REINDEX CONCURRENTLY will update the stored version.
+UPDATE pg_depend SET refobjversion = 'not a version'
+WHERE refclassid = 'pg_collation'::regclass
+AND objid::regclass::text LIKE 'icuidx%'
+AND refobjversion IS NOT NULL;
+REINDEX TABLE CONCURRENTLY collate_test;
+REINDEX TABLE CONCURRENTLY collate_part_0;
+REINDEX INDEX CONCURRENTLY icuidx17_part;
+
+SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version';
+
+-- Validate that VACUUM FULL will update the stored version.
+UPDATE pg_depend SET refobjversion = 'not a version'
+WHERE refclassid = 'pg_collation'::regclass
+AND objid::regclass::text LIKE 'icuidx%'
+AND refobjversion IS NOT NULL;
+VACUUM FULL collate_test;
+VACUUM FULL collate_part_0;
+VACUUM FULL collate_part_1;
+
+SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version';
 
 -- cleanup
 RESET search_path;
-- 
2.20.1


--lrZ03NoBR/3+SXJZ
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment;
	filename="v25-0004-Add-ALTER-INDEX-.-ALTER-COLLATION-.-REFRESH-VERS.patch"



^ permalink  raw  reply  [nested|flat] 2+ messages in thread

* Reorganize cache memory contexts
@ 2024-10-29 20:29  Jeff Davis <[email protected]>
  0 siblings, 0 replies; 2+ messages in thread

From: Jeff Davis @ 2024-10-29 20:29 UTC (permalink / raw)
  To: pgsql-hackers

Attached is a patch that reorganizes CacheMemoryContext to be mostly a
parent context, with child contexts such as CatCacheContext,
RelCacheContext, PlanCacheContext, etc.

The goal is to better account for specific caches, which will hopefully
lead to optimizations or other improvements.

Regards,
	Jeff Davis



Attachments:

  [text/x-patch] v1-0001-Reorganize-cache-memory-contexts.patch (46.9K, ../../[email protected]/2-v1-0001-Reorganize-cache-memory-contexts.patch)
  download | inline diff:
From 80febd60550d1710186a111d95659d0f80cece4e Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Tue, 29 Oct 2024 10:32:38 -0700
Subject: [PATCH v1] Reorganize cache memory contexts.

Create new memory contexts such as CatCacheContext, RelCacheContext,
etc. as children of CacheMemoryContext. Also, place other assorted
cache contexts, such as CollationCacheContext, under
CacheMemoryContext instead of TopMemoryContext.

This improves accounting, and may lead to optimizations for specific
caches.
---
 src/backend/catalog/namespace.c             |  5 +-
 src/backend/commands/policy.c               |  4 +-
 src/backend/commands/tablecmds.c            |  2 +-
 src/backend/commands/trigger.c              |  2 +-
 src/backend/executor/spi.c                  | 21 ++++-
 src/backend/foreign/foreign.c               |  4 +-
 src/backend/partitioning/partdesc.c         |  2 +-
 src/backend/replication/pgoutput/pgoutput.c | 14 ++-
 src/backend/utils/adt/pg_locale.c           |  6 +-
 src/backend/utils/cache/attoptcache.c       |  6 +-
 src/backend/utils/cache/catcache.c          | 36 +++++---
 src/backend/utils/cache/partcache.c         | 10 +--
 src/backend/utils/cache/plancache.c         | 36 +++++---
 src/backend/utils/cache/relcache.c          | 95 ++++++++++++---------
 src/backend/utils/cache/spccache.c          |  7 +-
 src/backend/utils/cache/ts_cache.c          | 47 ++++++----
 src/backend/utils/cache/typcache.c          | 62 ++++++++------
 src/include/utils/relcache.h                |  3 +
 18 files changed, 230 insertions(+), 132 deletions(-)

diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 30807f9190..f582325ac1 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -321,8 +321,11 @@ spcache_init(void)
 
 	if (SearchPathCacheContext == NULL)
 	{
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
 		/* Make the context we'll keep search path cache hashtable in */
-		SearchPathCacheContext = AllocSetContextCreate(TopMemoryContext,
+		SearchPathCacheContext = AllocSetContextCreate(CacheMemoryContext,
 													   "search_path processing cache",
 													   ALLOCSET_DEFAULT_SIZES);
 	}
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index 6ff3eba824..f83dc4c153 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -313,10 +313,10 @@ RelationBuildRowSecurity(Relation relation)
 
 	/*
 	 * Success.  Reparent the descriptor's memory context under
-	 * CacheMemoryContext so that it will live indefinitely, then attach the
+	 * RelCacheContext so that it will live indefinitely, then attach the
 	 * policy descriptor to the relcache entry.
 	 */
-	MemoryContextSetParent(rscxt, CacheMemoryContext);
+	MemoryContextSetParent(rscxt, RelCacheContext);
 
 	relation->rd_rsdesc = rsdesc;
 }
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index e14bc0c054..a45f6b73d6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -17615,7 +17615,7 @@ register_on_commit_action(Oid relid, OnCommitAction action)
 	if (action == ONCOMMIT_NOOP || action == ONCOMMIT_PRESERVE_ROWS)
 		return;
 
-	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	oldcxt = MemoryContextSwitchTo(RelCacheContext);
 
 	oc = (OnCommitItem *) palloc(sizeof(OnCommitItem));
 	oc->relid = relid;
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 09356e46d1..2ebbd38390 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -1994,7 +1994,7 @@ RelationBuildTriggers(Relation relation)
 		SetTriggerFlags(trigdesc, &(triggers[i]));
 
 	/* Copy completed trigdesc into cache storage */
-	oldContext = MemoryContextSwitchTo(CacheMemoryContext);
+	oldContext = MemoryContextSwitchTo(RelCacheContext);
 	relation->trigdesc = CopyTriggerDesc(trigdesc);
 	MemoryContextSwitchTo(oldContext);
 
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 2fb2e73604..f8e120f367 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -50,6 +50,8 @@ static _SPI_connection *_SPI_current = NULL;
 static int	_SPI_stack_depth = 0;	/* allocated size of _SPI_stack */
 static int	_SPI_connected = -1;	/* current stack index */
 
+static MemoryContext SPICacheContext = NULL;
+
 typedef struct SPICallbackArg
 {
 	const char *query;
@@ -972,6 +974,15 @@ SPI_prepare_params(const char *src,
 	return result;
 }
 
+static void
+CreateSPICacheContext(void)
+{
+	if (!SPICacheContext)
+		SPICacheContext = AllocSetContextCreate(CacheMemoryContext,
+												"SPICacheContext",
+												ALLOCSET_DEFAULT_SIZES);
+}
+
 int
 SPI_keepplan(SPIPlanPtr plan)
 {
@@ -981,13 +992,16 @@ SPI_keepplan(SPIPlanPtr plan)
 		plan->saved || plan->oneshot)
 		return SPI_ERROR_ARGUMENT;
 
+	if (!SPICacheContext)
+		CreateSPICacheContext();
+
 	/*
 	 * Mark it saved, reparent it under CacheMemoryContext, and mark all the
 	 * component CachedPlanSources as saved.  This sequence cannot fail
 	 * partway through, so there's no risk of long-term memory leakage.
 	 */
 	plan->saved = true;
-	MemoryContextSetParent(plan->plancxt, CacheMemoryContext);
+	MemoryContextSetParent(plan->plancxt, SPICacheContext);
 
 	foreach(lc, plan->plancache_list)
 	{
@@ -3255,13 +3269,16 @@ _SPI_save_plan(SPIPlanPtr plan)
 
 	MemoryContextSwitchTo(oldcxt);
 
+	if (!SPICacheContext)
+		CreateSPICacheContext();
+
 	/*
 	 * Mark it saved, reparent it under CacheMemoryContext, and mark all the
 	 * component CachedPlanSources as saved.  This sequence cannot fail
 	 * partway through, so there's no risk of long-term memory leakage.
 	 */
 	newplan->saved = true;
-	MemoryContextSetParent(newplan->plancxt, CacheMemoryContext);
+	MemoryContextSetParent(newplan->plancxt, SPICacheContext);
 
 	foreach(lc, newplan->plancache_list)
 	{
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index 4c06e1ff1c..dbc34dabd8 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -449,8 +449,8 @@ GetFdwRoutineForRelation(Relation relation, bool makecopy)
 		/* Get the info by consulting the catalogs and the FDW code */
 		fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(relation));
 
-		/* Save the data for later reuse in CacheMemoryContext */
-		cfdwroutine = (FdwRoutine *) MemoryContextAlloc(CacheMemoryContext,
+		/* Save the data for later reuse in RelCacheContext */
+		cfdwroutine = (FdwRoutine *) MemoryContextAlloc(RelCacheContext,
 														sizeof(FdwRoutine));
 		memcpy(cfdwroutine, fdwroutine, sizeof(FdwRoutine));
 		relation->rd_fdwroutine = cfdwroutine;
diff --git a/src/backend/partitioning/partdesc.c b/src/backend/partitioning/partdesc.c
index b4e0ed0e71..e5830bd337 100644
--- a/src/backend/partitioning/partdesc.c
+++ b/src/backend/partitioning/partdesc.c
@@ -373,7 +373,7 @@ retry:
 	 * We have a fully valid partdesc.  Reparent it so that it has the right
 	 * lifespan.
 	 */
-	MemoryContextSetParent(new_pdcxt, CacheMemoryContext);
+	MemoryContextSetParent(new_pdcxt, RelCacheContext);
 
 	/*
 	 * Store it into relcache.
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 00e7024563..bb6f788e62 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -209,6 +209,8 @@ typedef struct PGOutputTxnData
 /* Map used to remember which relation schemas we sent. */
 static HTAB *RelationSyncCache = NULL;
 
+static MemoryContext PgOutputCacheContext = NULL;
+
 static void init_rel_sync_cache(MemoryContext cachectx);
 static void cleanup_rel_sync_cache(TransactionId xid, bool is_commit);
 static RelationSyncEntry *get_rel_sync_entry(PGOutputData *data,
@@ -435,6 +437,10 @@ pgoutput_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
 	/* This plugin uses binary protocol. */
 	opt->output_type = OUTPUT_PLUGIN_BINARY_OUTPUT;
 
+	PgOutputCacheContext = AllocSetContextCreate(CacheMemoryContext,
+												 "pgoutput cache context",
+												 ALLOCSET_DEFAULT_SIZES);
+
 	/*
 	 * This is replication start and not slot initialization.
 	 *
@@ -521,7 +527,7 @@ pgoutput_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt,
 		}
 
 		/* Initialize relation schema cache. */
-		init_rel_sync_cache(CacheMemoryContext);
+		init_rel_sync_cache(PgOutputCacheContext);
 	}
 	else
 	{
@@ -1159,7 +1165,7 @@ init_tuple_slot(PGOutputData *data, Relation relation,
 		TupleDesc	outdesc = RelationGetDescr(ancestor);
 
 		/* Map must live as long as the session does. */
-		oldctx = MemoryContextSwitchTo(CacheMemoryContext);
+		oldctx = MemoryContextSwitchTo(PgOutputCacheContext);
 
 		entry->attrmap = build_attrmap_by_name_if_req(indesc, outdesc, false);
 
@@ -1953,7 +1959,7 @@ set_schema_sent_in_streamed_txn(RelationSyncEntry *entry, TransactionId xid)
 {
 	MemoryContext oldctx;
 
-	oldctx = MemoryContextSwitchTo(CacheMemoryContext);
+	oldctx = MemoryContextSwitchTo(PgOutputCacheContext);
 
 	entry->streamed_txns = lappend_xid(entry->streamed_txns, xid);
 
@@ -2024,7 +2030,7 @@ get_rel_sync_entry(PGOutputData *data, Relation relation)
 		/* Reload publications if needed before use. */
 		if (!publications_valid)
 		{
-			oldctx = MemoryContextSwitchTo(CacheMemoryContext);
+			oldctx = MemoryContextSwitchTo(PgOutputCacheContext);
 			if (data->publications)
 			{
 				list_free_deep(data->publications);
diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index d4e89663ec..3653d43c1d 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -62,6 +62,7 @@
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "utils/builtins.h"
+#include "utils/catcache.h"
 #include "utils/formatting.h"
 #include "utils/guc_hooks.h"
 #include "utils/lsyscache.h"
@@ -1464,7 +1465,10 @@ pg_newlocale_from_collation(Oid collid)
 
 	if (CollationCache == NULL)
 	{
-		CollationCacheContext = AllocSetContextCreate(TopMemoryContext,
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		CollationCacheContext = AllocSetContextCreate(CacheMemoryContext,
 													  "collation cache",
 													  ALLOCSET_DEFAULT_SIZES);
 		CollationCache = collation_cache_create(CollationCacheContext,
diff --git a/src/backend/utils/cache/attoptcache.c b/src/backend/utils/cache/attoptcache.c
index 259865d5b3..01d9139174 100644
--- a/src/backend/utils/cache/attoptcache.c
+++ b/src/backend/utils/cache/attoptcache.c
@@ -114,8 +114,8 @@ InitializeAttoptCache(void)
 					HASH_ELEM | HASH_FUNCTION);
 
 	/* Make sure we've initialized CacheMemoryContext. */
-	if (!CacheMemoryContext)
-		CreateCacheMemoryContext();
+	if (!RelCacheContext)
+		CreateRelCacheContext();
 
 	/* Watch for invalidation events. */
 	CacheRegisterSyscacheCallback(ATTNUM,
@@ -178,7 +178,7 @@ get_attribute_options(Oid attrelid, int attnum)
 			{
 				bytea	   *bytea_opts = attribute_reloptions(datum, false);
 
-				opts = MemoryContextAlloc(CacheMemoryContext,
+				opts = MemoryContextAlloc(RelCacheContext,
 										  VARSIZE(bytea_opts));
 				memcpy(opts, bytea_opts, VARSIZE(bytea_opts));
 			}
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index ee303dc501..9c7890ab03 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -62,6 +62,7 @@
 #endif
 
 /* Cache management header --- pointer is NULL until created */
+static MemoryContext CatCacheContext = NULL;
 static CatCacheHeader *CacheHdr = NULL;
 
 static inline HeapTuple SearchCatCacheInternal(CatCache *cache,
@@ -689,6 +690,17 @@ CreateCacheMemoryContext(void)
 												   ALLOCSET_DEFAULT_SIZES);
 }
 
+static void
+CreateCatCacheContext(void)
+{
+	if (!CacheMemoryContext)
+		CreateCacheMemoryContext();
+
+	if (!CatCacheContext)
+		CatCacheContext = AllocSetContextCreate(CacheMemoryContext,
+												"CatCacheContext",
+												ALLOCSET_DEFAULT_SIZES);
+}
 
 /*
  *		ResetCatalogCache
@@ -853,10 +865,10 @@ InitCatCache(int id,
 	 * first switch to the cache context so our allocations do not vanish at
 	 * the end of a transaction
 	 */
-	if (!CacheMemoryContext)
-		CreateCacheMemoryContext();
+	if (!CatCacheContext)
+		CreateCatCacheContext();
 
-	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	oldcxt = MemoryContextSwitchTo(CatCacheContext);
 
 	/*
 	 * if first time through, initialize the cache group header
@@ -943,7 +955,7 @@ RehashCatCache(CatCache *cp)
 
 	/* Allocate a new, larger, hash table. */
 	newnbuckets = cp->cc_nbuckets * 2;
-	newbucket = (dlist_head *) MemoryContextAllocZero(CacheMemoryContext, newnbuckets * sizeof(dlist_head));
+	newbucket = (dlist_head *) MemoryContextAllocZero(CatCacheContext, newnbuckets * sizeof(dlist_head));
 
 	/* Move all entries from old hash table to new. */
 	for (i = 0; i < cp->cc_nbuckets; i++)
@@ -981,7 +993,7 @@ RehashCatCacheLists(CatCache *cp)
 
 	/* Allocate a new, larger, hash table. */
 	newnbuckets = cp->cc_nlbuckets * 2;
-	newbucket = (dlist_head *) MemoryContextAllocZero(CacheMemoryContext, newnbuckets * sizeof(dlist_head));
+	newbucket = (dlist_head *) MemoryContextAllocZero(CatCacheContext, newnbuckets * sizeof(dlist_head));
 
 	/* Move all entries from old hash table to new. */
 	for (i = 0; i < cp->cc_nlbuckets; i++)
@@ -1048,9 +1060,9 @@ CatalogCacheInitializeCache(CatCache *cache)
 	 * switch to the cache context so our allocations do not vanish at the end
 	 * of a transaction
 	 */
-	Assert(CacheMemoryContext != NULL);
+	Assert(CatCacheContext != NULL);
 
-	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	oldcxt = MemoryContextSwitchTo(CatCacheContext);
 
 	/*
 	 * copy the relcache's tuple descriptor to permanent cache storage
@@ -1111,7 +1123,7 @@ CatalogCacheInitializeCache(CatCache *cache)
 		 */
 		fmgr_info_cxt(eqfunc,
 					  &cache->cc_skey[i].sk_func,
-					  CacheMemoryContext);
+					  CatCacheContext);
 
 		/* Initialize sk_attno suitably for HeapKeyTest() and heap scans */
 		cache->cc_skey[i].sk_attno = cache->cc_keyno[i];
@@ -1694,7 +1706,7 @@ SearchCatCacheList(CatCache *cache,
 		int			nbuckets = 16;
 
 		cache->cc_lbucket = (dlist_head *)
-			MemoryContextAllocZero(CacheMemoryContext,
+			MemoryContextAllocZero(CatCacheContext,
 								   nbuckets * sizeof(dlist_head));
 		/* Don't set cc_nlbuckets if we get OOM allocating cc_lbucket */
 		cache->cc_nlbuckets = nbuckets;
@@ -1896,7 +1908,7 @@ SearchCatCacheList(CatCache *cache,
 		ResourceOwnerEnlarge(CurrentResourceOwner);
 
 		/* Now we can build the CatCList entry. */
-		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+		oldcxt = MemoryContextSwitchTo(CatCacheContext);
 		nmembers = list_length(ctlist);
 		cl = (CatCList *)
 			palloc(offsetof(CatCList, members) + nmembers * sizeof(CatCTup *));
@@ -2110,7 +2122,7 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, SysScanDesc scandesc,
 			dtp = ntp;
 
 		/* Allocate memory for CatCTup and the cached tuple in one go */
-		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+		oldcxt = MemoryContextSwitchTo(CatCacheContext);
 
 		ct = (CatCTup *) palloc(sizeof(CatCTup) +
 								MAXIMUM_ALIGNOF + dtp->t_len);
@@ -2145,7 +2157,7 @@ CatalogCacheCreateEntry(CatCache *cache, HeapTuple ntp, SysScanDesc scandesc,
 	else
 	{
 		/* Set up keys for a negative cache entry */
-		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+		oldcxt = MemoryContextSwitchTo(CatCacheContext);
 		ct = (CatCTup *) palloc(sizeof(CatCTup));
 
 		/*
diff --git a/src/backend/utils/cache/partcache.c b/src/backend/utils/cache/partcache.c
index beec6cddbc..76b60c40f6 100644
--- a/src/backend/utils/cache/partcache.c
+++ b/src/backend/utils/cache/partcache.c
@@ -65,11 +65,11 @@ RelationGetPartitionKey(Relation rel)
  *
  * Partitioning key data is a complex structure; to avoid complicated logic to
  * free individual elements whenever the relcache entry is flushed, we give it
- * its own memory context, a child of CacheMemoryContext, which can easily be
+ * its own memory context, a child of RelCacheContext, which can easily be
  * deleted on its own.  To avoid leaking memory in that context in case of an
  * error partway through this function, the context is initially created as a
- * child of CurTransactionContext and only re-parented to CacheMemoryContext
- * at the end, when no further errors are possible.  Also, we don't make this
+ * child of CurTransactionContext and only re-parented to RelCacheContext at
+ * the end, when no further errors are possible.  Also, we don't make this
  * context the current context except in very brief code sections, out of fear
  * that some of our callees allocate memory on their own which would be leaked
  * permanently.
@@ -263,7 +263,7 @@ RelationBuildPartitionKey(Relation relation)
 	 * Success --- reparent our context and make the relcache point to the
 	 * newly constructed key
 	 */
-	MemoryContextSetParent(partkeycxt, CacheMemoryContext);
+	MemoryContextSetParent(partkeycxt, RelCacheContext);
 	relation->rd_partkeycxt = partkeycxt;
 	relation->rd_partkey = key;
 }
@@ -411,7 +411,7 @@ generate_partition_qual(Relation rel)
 	 */
 	if (result != NIL)
 	{
-		rel->rd_partcheckcxt = AllocSetContextCreate(CacheMemoryContext,
+		rel->rd_partcheckcxt = AllocSetContextCreate(RelCacheContext,
 													 "partition constraint",
 													 ALLOCSET_SMALL_SIZES);
 		MemoryContextCopyAndSetIdentifier(rel->rd_partcheckcxt,
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 5af1a168ec..cebf4e9483 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -99,6 +99,8 @@ static dlist_head saved_plan_list = DLIST_STATIC_INIT(saved_plan_list);
  */
 static dlist_head cached_expression_list = DLIST_STATIC_INIT(cached_expression_list);
 
+static MemoryContext PlanCacheContext = NULL;
+
 static void ReleaseGenericPlan(CachedPlanSource *plansource);
 static List *RevalidateCachedQuery(CachedPlanSource *plansource,
 								   QueryEnvironment *queryEnv);
@@ -463,10 +465,19 @@ CompleteCachedPlan(CachedPlanSource *plansource,
 	plansource->is_valid = true;
 }
 
+static void
+CreatePlanCacheContext(void)
+{
+	if (!PlanCacheContext)
+		PlanCacheContext = AllocSetContextCreate(CacheMemoryContext,
+												 "PlanCacheContext",
+												 ALLOCSET_DEFAULT_SIZES);
+}
+
 /*
  * SaveCachedPlan: save a cached plan permanently
  *
- * This function moves the cached plan underneath CacheMemoryContext (making
+ * This function moves the cached plan underneath PlanCacheContext (making
  * it live for the life of the backend, unless explicitly dropped), and adds
  * it to the list of cached plans that are checked for invalidation when an
  * sinval event occurs.
@@ -493,18 +504,21 @@ SaveCachedPlan(CachedPlanSource *plansource)
 	/*
 	 * In typical use, this function would be called before generating any
 	 * plans from the CachedPlanSource.  If there is a generic plan, moving it
-	 * into CacheMemoryContext would be pretty risky since it's unclear
+	 * into PlanCacheContext would be pretty risky since it's unclear
 	 * whether the caller has taken suitable care with making references
 	 * long-lived.  Best thing to do seems to be to discard the plan.
 	 */
 	ReleaseGenericPlan(plansource);
 
+	if (!PlanCacheContext)
+		CreatePlanCacheContext();
+
 	/*
-	 * Reparent the source memory context under CacheMemoryContext so that it
+	 * Reparent the source memory context under PlanCacheContext so that it
 	 * will live indefinitely.  The query_context follows along since it's
 	 * already a child of the other one.
 	 */
-	MemoryContextSetParent(plansource->context, CacheMemoryContext);
+	MemoryContextSetParent(plansource->context, PlanCacheContext);
 
 	/*
 	 * Add the entry to the global list of cached plans.
@@ -1205,8 +1219,8 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 			/* Immediately reparent into appropriate context */
 			if (plansource->is_saved)
 			{
-				/* saved plans all live under CacheMemoryContext */
-				MemoryContextSetParent(plan->context, CacheMemoryContext);
+				/* saved plans all live under PlanCacheContext */
+				MemoryContextSetParent(plan->context, PlanCacheContext);
 				plan->is_saved = true;
 			}
 			else
@@ -1262,14 +1276,14 @@ GetCachedPlan(CachedPlanSource *plansource, ParamListInfo boundParams,
 		ResourceOwnerRememberPlanCacheRef(owner, plan);
 
 	/*
-	 * Saved plans should be under CacheMemoryContext so they will not go away
+	 * Saved plans should be under PlanCacheContext so they will not go away
 	 * until their reference count goes to zero.  In the generic-plan cases we
 	 * already took care of that, but for a custom plan, do it as soon as we
 	 * have created a reference-counted link.
 	 */
 	if (customplan && plansource->is_saved)
 	{
-		MemoryContextSetParent(plan->context, CacheMemoryContext);
+		MemoryContextSetParent(plan->context, PlanCacheContext);
 		plan->is_saved = true;
 	}
 
@@ -1492,7 +1506,7 @@ CachedPlanIsSimplyValid(CachedPlanSource *plansource, CachedPlan *plan,
  * CachedPlanSetParentContext: move a CachedPlanSource to a new memory context
  *
  * This can only be applied to unsaved plans; once saved, a plan always
- * lives underneath CacheMemoryContext.
+ * lives underneath PlanCacheContext.
  */
 void
 CachedPlanSetParentContext(CachedPlanSource *plansource,
@@ -1713,10 +1727,10 @@ GetCachedExpression(Node *expr)
 	MemoryContextSwitchTo(oldcxt);
 
 	/*
-	 * Reparent the expr's memory context under CacheMemoryContext so that it
+	 * Reparent the expr's memory context under PlanCacheContext so that it
 	 * will live indefinitely.
 	 */
-	MemoryContextSetParent(cexpr_context, CacheMemoryContext);
+	MemoryContextSetParent(cexpr_context, PlanCacheContext);
 
 	/*
 	 * Add the entry to the global list of cached expressions.
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index c326f687eb..d2adf23d6f 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -145,6 +145,8 @@ bool		criticalRelcachesBuilt = false;
  */
 bool		criticalSharedRelcachesBuilt = false;
 
+MemoryContext RelCacheContext = NULL;
+
 /*
  * This counter counts relcache inval events received since backend startup
  * (but only for rels that are actually in cache).  Presently, we use it only
@@ -270,7 +272,6 @@ typedef struct opclasscacheent
 
 static HTAB *OpClassCache = NULL;
 
-
 /* non-export function prototypes */
 
 static void RelationCloseCleanup(Relation relation);
@@ -412,8 +413,8 @@ AllocateRelationDesc(Form_pg_class relp)
 	MemoryContext oldcxt;
 	Form_pg_class relationForm;
 
-	/* Relcache entries must live in CacheMemoryContext */
-	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	/* Relcache entries must live in RelCacheContext */
+	oldcxt = MemoryContextSwitchTo(RelCacheContext);
 
 	/*
 	 * allocate and zero space for new relation descriptor
@@ -497,14 +498,14 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 	options = extractRelOptions(tuple, GetPgClassDescriptor(), amoptsfn);
 
 	/*
-	 * Copy parsed data into CacheMemoryContext.  To guard against the
+	 * Copy parsed data into RelCacheContext.  To guard against the
 	 * possibility of leaks in the reloptions code, we want to do the actual
 	 * parsing in the caller's memory context and copy the results into
-	 * CacheMemoryContext after the fact.
+	 * RelCacheContext after the fact.
 	 */
 	if (options)
 	{
-		relation->rd_options = MemoryContextAlloc(CacheMemoryContext,
+		relation->rd_options = MemoryContextAlloc(RelCacheContext,
 												  VARSIZE(options));
 		memcpy(relation->rd_options, options, VARSIZE(options));
 		pfree(options);
@@ -534,7 +535,7 @@ RelationBuildTupleDesc(Relation relation)
 		relation->rd_rel->reltype ? relation->rd_rel->reltype : RECORDOID;
 	relation->rd_att->tdtypmod = -1;	/* just to be sure */
 
-	constr = (TupleConstr *) MemoryContextAllocZero(CacheMemoryContext,
+	constr = (TupleConstr *) MemoryContextAllocZero(RelCacheContext,
 													sizeof(TupleConstr));
 
 	/*
@@ -613,7 +614,7 @@ RelationBuildTupleDesc(Relation relation)
 
 				if (attrmiss == NULL)
 					attrmiss = (AttrMissing *)
-						MemoryContextAllocZero(CacheMemoryContext,
+						MemoryContextAllocZero(RelCacheContext,
 											   relation->rd_rel->relnatts *
 											   sizeof(AttrMissing));
 
@@ -634,7 +635,7 @@ RelationBuildTupleDesc(Relation relation)
 				else
 				{
 					/* otherwise copy in the correct context */
-					oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+					oldcxt = MemoryContextSwitchTo(RelCacheContext);
 					attrmiss[attnum - 1].am_value = datumCopy(missval,
 															  attp->attbyval,
 															  attp->attlen);
@@ -745,7 +746,7 @@ RelationBuildRuleLock(Relation relation)
 	/*
 	 * Make the private context.  Assume it'll not contain much data.
 	 */
-	rulescxt = AllocSetContextCreate(CacheMemoryContext,
+	rulescxt = AllocSetContextCreate(RelCacheContext,
 									 "relation rules",
 									 ALLOCSET_SMALL_SIZES);
 	relation->rd_rulescxt = rulescxt;
@@ -1448,7 +1449,7 @@ RelationInitIndexAccessInfo(Relation relation)
 	if (!HeapTupleIsValid(tuple))
 		elog(ERROR, "cache lookup failed for index %u",
 			 RelationGetRelid(relation));
-	oldcontext = MemoryContextSwitchTo(CacheMemoryContext);
+	oldcontext = MemoryContextSwitchTo(RelCacheContext);
 	relation->rd_indextuple = heap_copytuple(tuple);
 	relation->rd_index = (Form_pg_index) GETSTRUCT(relation->rd_indextuple);
 	MemoryContextSwitchTo(oldcontext);
@@ -1477,7 +1478,7 @@ RelationInitIndexAccessInfo(Relation relation)
 	 * a context, and not just a couple of pallocs, is so that we won't leak
 	 * any subsidiary info attached to fmgr lookup records.
 	 */
-	indexcxt = AllocSetContextCreate(CacheMemoryContext,
+	indexcxt = AllocSetContextCreate(RelCacheContext,
 									 "index info",
 									 ALLOCSET_SMALL_SIZES);
 	relation->rd_indexcxt = indexcxt;
@@ -1622,6 +1623,18 @@ IndexSupportInitialize(oidvector *indclass,
 	}
 }
 
+void
+CreateRelCacheContext(void)
+{
+	if (!CacheMemoryContext)
+		CreateCacheMemoryContext();
+
+	if (!RelCacheContext)
+		RelCacheContext = AllocSetContextCreate(CacheMemoryContext,
+												"RelCacheContext",
+												ALLOCSET_DEFAULT_SIZES);
+}
+
 /*
  * LookupOpclassInfo
  *
@@ -1659,9 +1672,9 @@ LookupOpclassInfo(Oid operatorClassOid,
 		/* First time through: initialize the opclass cache */
 		HASHCTL		ctl;
 
-		/* Also make sure CacheMemoryContext exists */
-		if (!CacheMemoryContext)
-			CreateCacheMemoryContext();
+		/* Also make sure RelCacheContext exists */
+		if (!RelCacheContext)
+			CreateRelCacheContext();
 
 		ctl.keysize = sizeof(Oid);
 		ctl.entrysize = sizeof(OpClassCacheEnt);
@@ -1708,7 +1721,7 @@ LookupOpclassInfo(Oid operatorClassOid,
 	 */
 	if (opcentry->supportProcs == NULL && numSupport > 0)
 		opcentry->supportProcs = (RegProcedure *)
-			MemoryContextAllocZero(CacheMemoryContext,
+			MemoryContextAllocZero(RelCacheContext,
 								   numSupport * sizeof(RegProcedure));
 
 	/*
@@ -1867,7 +1880,7 @@ RelationInitTableAccessMethod(Relation relation)
  * during bootstrap or before RelationCacheInitializePhase3 runs, and none of
  * these properties matter then...)
  *
- * NOTE: we assume we are already switched into CacheMemoryContext.
+ * NOTE: we assume we are already switched into RelCacheContext.
  */
 static void
 formrdesc(const char *relationName, Oid relationReltype,
@@ -3116,7 +3129,7 @@ RememberToFreeTupleDescAtEOX(TupleDesc td)
 	{
 		MemoryContext oldcxt;
 
-		oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+		oldcxt = MemoryContextSwitchTo(RelCacheContext);
 
 		EOXactTupleDescArray = (TupleDesc *) palloc(16 * sizeof(TupleDesc));
 		EOXactTupleDescArrayLen = 16;
@@ -3580,10 +3593,10 @@ RelationBuildLocalRelation(const char *relname,
 	/*
 	 * switch to the cache context to create the relcache entry.
 	 */
-	if (!CacheMemoryContext)
-		CreateCacheMemoryContext();
+	if (!RelCacheContext)
+		CreateRelCacheContext();
 
-	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	oldcxt = MemoryContextSwitchTo(RelCacheContext);
 
 	/*
 	 * allocate a new relation descriptor and fill in basic state fields.
@@ -3712,7 +3725,7 @@ RelationBuildLocalRelation(const char *relname,
 
 	/*
 	 * RelationInitTableAccessMethod will do syscache lookups, so we mustn't
-	 * run it in CacheMemoryContext.  Fortunately, the remaining steps don't
+	 * run it in RelCacheContext.  Fortunately, the remaining steps don't
 	 * require a long-lived current context.
 	 */
 	MemoryContextSwitchTo(oldcxt);
@@ -4000,8 +4013,8 @@ RelationCacheInitialize(void)
 	/*
 	 * make sure cache memory context exists
 	 */
-	if (!CacheMemoryContext)
-		CreateCacheMemoryContext();
+	if (!RelCacheContext)
+		CreateRelCacheContext();
 
 	/*
 	 * create hashtable that indexes the relcache
@@ -4016,7 +4029,7 @@ RelationCacheInitialize(void)
 	 */
 	allocsize = 4;
 	in_progress_list =
-		MemoryContextAlloc(CacheMemoryContext,
+		MemoryContextAlloc(RelCacheContext,
 						   allocsize * sizeof(*in_progress_list));
 	in_progress_list_maxlen = allocsize;
 
@@ -4057,7 +4070,7 @@ RelationCacheInitializePhase2(void)
 	/*
 	 * switch to cache memory context
 	 */
-	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	oldcxt = MemoryContextSwitchTo(RelCacheContext);
 
 	/*
 	 * Try to load the shared relcache cache file.  If unsuccessful, bootstrap
@@ -4112,7 +4125,7 @@ RelationCacheInitializePhase3(void)
 	/*
 	 * switch to cache memory context
 	 */
-	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	oldcxt = MemoryContextSwitchTo(RelCacheContext);
 
 	/*
 	 * Try to load the local relcache cache file.  If unsuccessful, bootstrap
@@ -4426,7 +4439,7 @@ BuildHardcodedDescriptor(int natts, const FormData_pg_attribute *attrs)
 	MemoryContext oldcxt;
 	int			i;
 
-	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	oldcxt = MemoryContextSwitchTo(RelCacheContext);
 
 	result = CreateTemplateTupleDesc(natts);
 	result->tdtypeid = RECORDOID;	/* not right, but we don't care */
@@ -4496,7 +4509,7 @@ AttrDefaultFetch(Relation relation, int ndef)
 
 	/* Allocate array with room for as many entries as expected */
 	attrdef = (AttrDefault *)
-		MemoryContextAllocZero(CacheMemoryContext,
+		MemoryContextAllocZero(RelCacheContext,
 							   ndef * sizeof(AttrDefault));
 
 	/* Search pg_attrdef for relevant entries */
@@ -4535,7 +4548,7 @@ AttrDefaultFetch(Relation relation, int ndef)
 			char	   *s = TextDatumGetCString(val);
 
 			attrdef[found].adnum = adform->adnum;
-			attrdef[found].adbin = MemoryContextStrdup(CacheMemoryContext, s);
+			attrdef[found].adbin = MemoryContextStrdup(RelCacheContext, s);
 			pfree(s);
 			found++;
 		}
@@ -4592,7 +4605,7 @@ CheckConstraintFetch(Relation relation)
 
 	/* Allocate array with room for as many entries as expected */
 	check = (ConstrCheck *)
-		MemoryContextAllocZero(CacheMemoryContext,
+		MemoryContextAllocZero(RelCacheContext,
 							   ncheck * sizeof(ConstrCheck));
 
 	/* Search pg_constraint for relevant entries */
@@ -4625,7 +4638,7 @@ CheckConstraintFetch(Relation relation)
 
 		check[found].ccvalid = conform->convalidated;
 		check[found].ccnoinherit = conform->connoinherit;
-		check[found].ccname = MemoryContextStrdup(CacheMemoryContext,
+		check[found].ccname = MemoryContextStrdup(RelCacheContext,
 												  NameStr(conform->conname));
 
 		/* Grab and test conbin is actually set */
@@ -4640,7 +4653,7 @@ CheckConstraintFetch(Relation relation)
 			/* detoast and convert to cstring in caller's context */
 			char	   *s = TextDatumGetCString(val);
 
-			check[found].ccbin = MemoryContextStrdup(CacheMemoryContext, s);
+			check[found].ccbin = MemoryContextStrdup(RelCacheContext, s);
 			pfree(s);
 			found++;
 		}
@@ -4757,7 +4770,7 @@ RelationGetFKeyList(Relation relation)
 	table_close(conrel, AccessShareLock);
 
 	/* Now save a copy of the completed list in the relcache entry. */
-	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	oldcxt = MemoryContextSwitchTo(RelCacheContext);
 	oldlist = relation->rd_fkeylist;
 	relation->rd_fkeylist = copyObject(result);
 	relation->rd_fkeyvalid = true;
@@ -4880,7 +4893,7 @@ RelationGetIndexList(Relation relation)
 	list_sort(result, list_oid_cmp);
 
 	/* Now save a copy of the completed list in the relcache entry. */
-	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	oldcxt = MemoryContextSwitchTo(RelCacheContext);
 	oldlist = relation->rd_indexlist;
 	relation->rd_indexlist = list_copy(result);
 	relation->rd_pkindex = pkeyIndex;
@@ -4972,7 +4985,7 @@ RelationGetStatExtList(Relation relation)
 	list_sort(result, list_oid_cmp);
 
 	/* Now save a copy of the completed list in the relcache entry. */
-	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	oldcxt = MemoryContextSwitchTo(RelCacheContext);
 	oldlist = relation->rd_statlist;
 	relation->rd_statlist = list_copy(result);
 
@@ -5473,7 +5486,7 @@ restart:
 	 * leave the relcache entry looking like the other ones are valid but
 	 * empty.
 	 */
-	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	oldcxt = MemoryContextSwitchTo(RelCacheContext);
 	relation->rd_keyattr = bms_copy(uindexattrs);
 	relation->rd_pkattr = bms_copy(pkindexattrs);
 	relation->rd_idattr = bms_copy(idindexattrs);
@@ -5573,7 +5586,7 @@ RelationGetIdentityKeyBitmap(Relation relation)
 	relation->rd_idattr = NULL;
 
 	/* Now save copy of the bitmap in the relcache entry */
-	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	oldcxt = MemoryContextSwitchTo(RelCacheContext);
 	relation->rd_idattr = bms_copy(idindexattrs);
 	MemoryContextSwitchTo(oldcxt);
 
@@ -5870,7 +5883,7 @@ RelationBuildPublicationDesc(Relation relation, PublicationDesc *pubdesc)
 	}
 
 	/* Now save copy of the descriptor in the relcache entry. */
-	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	oldcxt = MemoryContextSwitchTo(RelCacheContext);
 	relation->rd_pubdesc = palloc(sizeof(PublicationDesc));
 	memcpy(relation->rd_pubdesc, pubdesc, sizeof(PublicationDesc));
 	MemoryContextSwitchTo(oldcxt);
@@ -6073,7 +6086,7 @@ errtableconstraint(Relation rel, const char *conname)
  * criticalSharedRelcachesBuilt to true.
  * If not successful, return false.
  *
- * NOTE: we assume we are already switched into CacheMemoryContext.
+ * NOTE: we assume we are already switched into RelCacheContext.
  */
 static bool
 load_relcache_init_file(bool shared)
@@ -6242,7 +6255,7 @@ load_relcache_init_file(bool shared)
 			 * prepare index info context --- parameters should match
 			 * RelationInitIndexAccessInfo
 			 */
-			indexcxt = AllocSetContextCreate(CacheMemoryContext,
+			indexcxt = AllocSetContextCreate(RelCacheContext,
 											 "index info",
 											 ALLOCSET_SMALL_SIZES);
 			rel->rd_indexcxt = indexcxt;
diff --git a/src/backend/utils/cache/spccache.c b/src/backend/utils/cache/spccache.c
index ec63cdc8e5..2be8d4a792 100644
--- a/src/backend/utils/cache/spccache.c
+++ b/src/backend/utils/cache/spccache.c
@@ -86,9 +86,8 @@ InitializeTableSpaceCache(void)
 		hash_create("TableSpace cache", 16, &ctl,
 					HASH_ELEM | HASH_BLOBS);
 
-	/* Make sure we've initialized CacheMemoryContext. */
-	if (!CacheMemoryContext)
-		CreateCacheMemoryContext();
+	if (!RelCacheContext)
+		CreateRelCacheContext();
 
 	/* Watch for invalidation events. */
 	CacheRegisterSyscacheCallback(TABLESPACEOID,
@@ -151,7 +150,7 @@ get_tablespace(Oid spcid)
 		{
 			bytea	   *bytea_opts = tablespace_reloptions(datum, false);
 
-			opts = MemoryContextAlloc(CacheMemoryContext, VARSIZE(bytea_opts));
+			opts = MemoryContextAlloc(RelCacheContext, VARSIZE(bytea_opts));
 			memcpy(opts, bytea_opts, VARSIZE(bytea_opts));
 		}
 		ReleaseSysCache(tp);
diff --git a/src/backend/utils/cache/ts_cache.c b/src/backend/utils/cache/ts_cache.c
index 54de33eadd..4429bd9f2d 100644
--- a/src/backend/utils/cache/ts_cache.c
+++ b/src/backend/utils/cache/ts_cache.c
@@ -75,6 +75,7 @@ static TSConfigCacheEntry *lastUsedConfig = NULL;
  */
 char	   *TSCurrentConfig = NULL;
 
+static MemoryContext TextSearchCacheContext = NULL;
 static Oid	TSCurrentConfigCache = InvalidOid;
 
 
@@ -106,6 +107,18 @@ InvalidateTSCacheCallBack(Datum arg, int cacheid, uint32 hashvalue)
 		TSCurrentConfigCache = InvalidOid;
 }
 
+static void
+CreateTextSearchCacheContext(void)
+{
+	if (!CacheMemoryContext)
+		CreateCacheMemoryContext();
+
+	if (!TextSearchCacheContext)
+		TextSearchCacheContext = AllocSetContextCreate(CacheMemoryContext,
+													   "TextSearchCacheContext",
+													   ALLOCSET_DEFAULT_SIZES);
+}
+
 /*
  * Fetch parser cache entry
  */
@@ -127,9 +140,9 @@ lookup_ts_parser_cache(Oid prsId)
 		CacheRegisterSyscacheCallback(TSPARSEROID, InvalidateTSCacheCallBack,
 									  PointerGetDatum(TSParserCacheHash));
 
-		/* Also make sure CacheMemoryContext exists */
-		if (!CacheMemoryContext)
-			CreateCacheMemoryContext();
+		/* Also make sure TextSearchCacheContext exists */
+		if (!TextSearchCacheContext)
+			CreateTextSearchCacheContext();
 	}
 
 	/* Check single-entry cache */
@@ -186,12 +199,12 @@ lookup_ts_parser_cache(Oid prsId)
 
 		ReleaseSysCache(tp);
 
-		fmgr_info_cxt(entry->startOid, &entry->prsstart, CacheMemoryContext);
-		fmgr_info_cxt(entry->tokenOid, &entry->prstoken, CacheMemoryContext);
-		fmgr_info_cxt(entry->endOid, &entry->prsend, CacheMemoryContext);
+		fmgr_info_cxt(entry->startOid, &entry->prsstart, TextSearchCacheContext);
+		fmgr_info_cxt(entry->tokenOid, &entry->prstoken, TextSearchCacheContext);
+		fmgr_info_cxt(entry->endOid, &entry->prsend, TextSearchCacheContext);
 		if (OidIsValid(entry->headlineOid))
 			fmgr_info_cxt(entry->headlineOid, &entry->prsheadline,
-						  CacheMemoryContext);
+						  TextSearchCacheContext);
 
 		entry->isvalid = true;
 	}
@@ -224,9 +237,9 @@ lookup_ts_dictionary_cache(Oid dictId)
 		CacheRegisterSyscacheCallback(TSTEMPLATEOID, InvalidateTSCacheCallBack,
 									  PointerGetDatum(TSDictionaryCacheHash));
 
-		/* Also make sure CacheMemoryContext exists */
-		if (!CacheMemoryContext)
-			CreateCacheMemoryContext();
+		/* Also make sure TextSearchCacheContext exists */
+		if (!TextSearchCacheContext)
+			CreateTextSearchCacheContext();
 	}
 
 	/* Check single-entry cache */
@@ -291,7 +304,7 @@ lookup_ts_dictionary_cache(Oid dictId)
 			Assert(!found);		/* it wasn't there a moment ago */
 
 			/* Create private memory context the first time through */
-			saveCtx = AllocSetContextCreate(CacheMemoryContext,
+			saveCtx = AllocSetContextCreate(TextSearchCacheContext,
 											"TS dictionary",
 											ALLOCSET_SMALL_SIZES);
 			MemoryContextCopyAndSetIdentifier(saveCtx, NameStr(dict->dictname));
@@ -373,9 +386,9 @@ init_ts_config_cache(void)
 	CacheRegisterSyscacheCallback(TSCONFIGMAP, InvalidateTSCacheCallBack,
 								  PointerGetDatum(TSConfigCacheHash));
 
-	/* Also make sure CacheMemoryContext exists */
-	if (!CacheMemoryContext)
-		CreateCacheMemoryContext();
+	/* Also make sure TextSearchCacheContext exists */
+	if (!TextSearchCacheContext)
+		CreateTextSearchCacheContext();
 }
 
 /*
@@ -498,7 +511,7 @@ lookup_ts_config_cache(Oid cfgId)
 				{
 					maplists[maxtokentype].len = ndicts;
 					maplists[maxtokentype].dictIds = (Oid *)
-						MemoryContextAlloc(CacheMemoryContext,
+						MemoryContextAlloc(TextSearchCacheContext,
 										   sizeof(Oid) * ndicts);
 					memcpy(maplists[maxtokentype].dictIds, mapdicts,
 						   sizeof(Oid) * ndicts);
@@ -525,14 +538,14 @@ lookup_ts_config_cache(Oid cfgId)
 			/* save the last token type's dictionaries */
 			maplists[maxtokentype].len = ndicts;
 			maplists[maxtokentype].dictIds = (Oid *)
-				MemoryContextAlloc(CacheMemoryContext,
+				MemoryContextAlloc(TextSearchCacheContext,
 								   sizeof(Oid) * ndicts);
 			memcpy(maplists[maxtokentype].dictIds, mapdicts,
 				   sizeof(Oid) * ndicts);
 			/* and save the overall map */
 			entry->lenmap = maxtokentype + 1;
 			entry->map = (ListDictionary *)
-				MemoryContextAlloc(CacheMemoryContext,
+				MemoryContextAlloc(TextSearchCacheContext,
 								   sizeof(ListDictionary) * entry->lenmap);
 			memcpy(entry->map, maplists,
 				   sizeof(ListDictionary) * entry->lenmap);
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 1972bd1944..c80a3e1f20 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -78,6 +78,8 @@
 /* The main type cache hashtable searched by lookup_type_cache */
 static HTAB *TypeCacheHash = NULL;
 
+static MemoryContext TypCacheContext = NULL;
+
 /*
  * The mapping of relation's OID to the corresponding composite type OID.
  * We're keeping the map entry when the corresponding typentry has something
@@ -362,6 +364,18 @@ type_cache_syshash(const void *key, Size keysize)
 	return GetSysCacheHashValue1(TYPEOID, ObjectIdGetDatum(*(const Oid *) key));
 }
 
+static void
+CreateTypCacheContext(void)
+{
+	if (!CacheMemoryContext)
+		CreateCacheMemoryContext();
+
+	if (!TypCacheContext)
+		TypCacheContext = AllocSetContextCreate(CacheMemoryContext,
+												"TypCacheContext",
+												ALLOCSET_DEFAULT_SIZES);
+}
+
 /*
  * lookup_type_cache
  *
@@ -421,16 +435,16 @@ lookup_type_cache(Oid type_id, int flags)
 		CacheRegisterSyscacheCallback(CLAOID, TypeCacheOpcCallback, (Datum) 0);
 		CacheRegisterSyscacheCallback(CONSTROID, TypeCacheConstrCallback, (Datum) 0);
 
-		/* Also make sure CacheMemoryContext exists */
-		if (!CacheMemoryContext)
-			CreateCacheMemoryContext();
+		/* Also make sure TypCacheContext exists */
+		if (!TypCacheContext)
+			CreateTypCacheContext();
 
 		/*
 		 * reserve enough in_progress_list slots for many cases
 		 */
 		allocsize = 4;
 		in_progress_list =
-			MemoryContextAlloc(CacheMemoryContext,
+			MemoryContextAlloc(TypCacheContext,
 							   allocsize * sizeof(*in_progress_list));
 		in_progress_list_maxlen = allocsize;
 	}
@@ -854,7 +868,7 @@ lookup_type_cache(Oid type_id, int flags)
 	/*
 	 * Set up fmgr lookup info as requested
 	 *
-	 * Note: we tell fmgr the finfo structures live in CacheMemoryContext,
+	 * Note: we tell fmgr the finfo structures live in TypCacheContext,
 	 * which is not quite right (they're really in the hash table's private
 	 * memory context) but this will do for our purposes.
 	 *
@@ -872,21 +886,21 @@ lookup_type_cache(Oid type_id, int flags)
 		eq_opr_func = get_opcode(typentry->eq_opr);
 		if (eq_opr_func != InvalidOid)
 			fmgr_info_cxt(eq_opr_func, &typentry->eq_opr_finfo,
-						  CacheMemoryContext);
+						  TypCacheContext);
 	}
 	if ((flags & TYPECACHE_CMP_PROC_FINFO) &&
 		typentry->cmp_proc_finfo.fn_oid == InvalidOid &&
 		typentry->cmp_proc != InvalidOid)
 	{
 		fmgr_info_cxt(typentry->cmp_proc, &typentry->cmp_proc_finfo,
-					  CacheMemoryContext);
+					  TypCacheContext);
 	}
 	if ((flags & TYPECACHE_HASH_PROC_FINFO) &&
 		typentry->hash_proc_finfo.fn_oid == InvalidOid &&
 		typentry->hash_proc != InvalidOid)
 	{
 		fmgr_info_cxt(typentry->hash_proc, &typentry->hash_proc_finfo,
-					  CacheMemoryContext);
+					  TypCacheContext);
 	}
 	if ((flags & TYPECACHE_HASH_EXTENDED_PROC_FINFO) &&
 		typentry->hash_extended_proc_finfo.fn_oid == InvalidOid &&
@@ -894,7 +908,7 @@ lookup_type_cache(Oid type_id, int flags)
 	{
 		fmgr_info_cxt(typentry->hash_extended_proc,
 					  &typentry->hash_extended_proc_finfo,
-					  CacheMemoryContext);
+					  TypCacheContext);
 	}
 
 	/*
@@ -1039,13 +1053,13 @@ load_rangetype_info(TypeCacheEntry *typentry)
 
 	/* set up cached fmgrinfo structs */
 	fmgr_info_cxt(cmpFnOid, &typentry->rng_cmp_proc_finfo,
-				  CacheMemoryContext);
+				  TypCacheContext);
 	if (OidIsValid(canonicalOid))
 		fmgr_info_cxt(canonicalOid, &typentry->rng_canonical_finfo,
-					  CacheMemoryContext);
+					  TypCacheContext);
 	if (OidIsValid(subdiffOid))
 		fmgr_info_cxt(subdiffOid, &typentry->rng_subdiff_finfo,
-					  CacheMemoryContext);
+					  TypCacheContext);
 
 	/* Lastly, set up link to the element type --- this marks data valid */
 	typentry->rngelemtype = lookup_type_cache(subtypeOid, 0);
@@ -1074,7 +1088,7 @@ load_multirangetype_info(TypeCacheEntry *typentry)
  * Note: we assume we're called in a relatively short-lived context, so it's
  * okay to leak data into the current context while scanning pg_constraint.
  * We build the new DomainConstraintCache data in a context underneath
- * CurrentMemoryContext, and reparent it under CacheMemoryContext when
+ * CurrentMemoryContext, and reparent it under TypCacheContext when
  * complete.
  */
 static void
@@ -1296,12 +1310,12 @@ load_domaintype_info(TypeCacheEntry *typentry)
 	}
 
 	/*
-	 * If we made a constraint object, move it into CacheMemoryContext and
+	 * If we made a constraint object, move it into TypCacheContext and
 	 * attach it to the typcache entry.
 	 */
 	if (dcc)
 	{
-		MemoryContextSetParent(dcc->dccContext, CacheMemoryContext);
+		MemoryContextSetParent(dcc->dccContext, TypCacheContext);
 		typentry->domainData = dcc;
 		dcc->dccRefCount++;		/* count the typcache's reference */
 	}
@@ -1799,7 +1813,7 @@ ensure_record_cache_typmod_slot_exists(int32 typmod)
 	if (RecordCacheArray == NULL)
 	{
 		RecordCacheArray = (RecordCacheArrayEntry *)
-			MemoryContextAllocZero(CacheMemoryContext,
+			MemoryContextAllocZero(TypCacheContext,
 								   64 * sizeof(RecordCacheArrayEntry));
 		RecordCacheArrayLen = 64;
 	}
@@ -2059,9 +2073,9 @@ assign_record_type_typmod(TupleDesc tupDesc)
 									  &ctl,
 									  HASH_ELEM | HASH_FUNCTION | HASH_COMPARE);
 
-		/* Also make sure CacheMemoryContext exists */
-		if (!CacheMemoryContext)
-			CreateCacheMemoryContext();
+		/* Also make sure TypCacheContext exists */
+		if (!TypCacheContext)
+			CreateTypCacheContext();
 	}
 
 	/*
@@ -2079,7 +2093,7 @@ assign_record_type_typmod(TupleDesc tupDesc)
 	}
 
 	/* Not present, so need to manufacture an entry */
-	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	oldcxt = MemoryContextSwitchTo(TypCacheContext);
 
 	/* Look in the SharedRecordTypmodRegistry, if attached */
 	entDesc = find_or_make_matching_shared_tupledesc(tupDesc);
@@ -2746,8 +2760,8 @@ load_enum_cache_data(TypeCacheEntry *tcache)
 	/*
 	 * Read all the information for members of the enum type.  We collect the
 	 * info in working memory in the caller's context, and then transfer it to
-	 * permanent memory in CacheMemoryContext.  This minimizes the risk of
-	 * leaking memory from CacheMemoryContext in the event of an error partway
+	 * permanent memory in TypCacheContext.  This minimizes the risk of
+	 * leaking memory from TypCacheContext in the event of an error partway
 	 * through.
 	 */
 	maxitems = 64;
@@ -2851,8 +2865,8 @@ load_enum_cache_data(TypeCacheEntry *tcache)
 			break;
 	}
 
-	/* OK, copy the data into CacheMemoryContext */
-	oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+	/* OK, copy the data into TypCacheContext */
+	oldcxt = MemoryContextSwitchTo(TypCacheContext);
 	enumdata = (TypeCacheEnumData *)
 		palloc(offsetof(TypeCacheEnumData, enum_values) +
 			   numitems * sizeof(EnumItem));
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 18c32ea700..ca089d3dbc 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -95,6 +95,7 @@ extern int	errtableconstraint(Relation rel, const char *conname);
 /*
  * Routines for backend startup
  */
+extern void CreateRelCacheContext(void);
 extern void RelationCacheInitialize(void);
 extern void RelationCacheInitializePhase2(void);
 extern void RelationCacheInitializePhase3(void);
@@ -152,4 +153,6 @@ extern PGDLLIMPORT bool criticalRelcachesBuilt;
 /* should be used only by relcache.c and postinit.c */
 extern PGDLLIMPORT bool criticalSharedRelcachesBuilt;
 
+extern PGDLLIMPORT MemoryContext RelCacheContext;
+
 #endif							/* RELCACHE_H */
-- 
2.34.1



^ permalink  raw  reply  [nested|flat] 2+ messages in thread


end of thread, other threads:[~2024-10-29 20:29 UTC | newest]

Thread overview: 2+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2019-05-28 18:16 [PATCH v25 3/5] Track collation versions for indexes. Thomas Munro <[email protected]>
2024-10-29 20:29 Reorganize cache memory contexts Jeff Davis <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox