From d85668b2ea0605cbfd20342a852a04057f76326b Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@enterprisedb.com>
Date: Mon, 24 Sep 2018 00:20:28 +1200
Subject: [PATCH 4/6] Track collation versions for CHECK constraints.

Record the current version of dependent collations in pg_depend when
creating CHECK constraints, and then warn if the collation versions
change.

Author: Thomas Munro
Reviewed-by:
Discussion: https://postgr.es/m/CAEepm%3D0uEQCpfq_%2BLYFBdArCe4Ot98t1aR4eYiYTe%3DyavQygiQ%40mail.gmail.com
---
 src/backend/catalog/dependency.c    | 28 +++++++++++++++++--
 src/backend/catalog/pg_constraint.c | 43 +++++++++++++++++++++++++++++
 src/backend/utils/cache/relcache.c  |  4 +++
 src/include/catalog/pg_constraint.h |  3 ++
 4 files changed, 76 insertions(+), 2 deletions(-)

diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index c674120eaf5..f924dbaf242 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -78,6 +78,7 @@
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
+#include "utils/pg_locale.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
 
@@ -1399,6 +1400,21 @@ ReleaseDeletionLock(const ObjectAddress *object)
 							 AccessExclusiveLock);
 }
 
+static NameData *
+capture_collation_versions(ObjectAddresses *addrs)
+{
+	NameData *results = palloc0(sizeof(NameData) * addrs->numrefs);
+
+	for (int i = 0; i < addrs->numrefs; ++i)
+	{
+		if (addrs->refs[i].classId != CollationRelationId)
+			continue;
+		get_collation_version_for_oid(addrs->refs[i].objectId, &results[i]);
+	}
+
+	return results;
+}
+
 /*
  * recordDependencyOnExpr - find expression dependencies
  *
@@ -1418,6 +1434,7 @@ recordDependencyOnExpr(const ObjectAddress *depender,
 					   Node *expr, List *rtable,
 					   DependencyType behavior)
 {
+	NameData *versions;
 	find_expr_references_context context;
 
 	context.addrs = new_object_addresses();
@@ -1431,9 +1448,12 @@ recordDependencyOnExpr(const ObjectAddress *depender,
 	/* Remove any duplicates */
 	eliminate_duplicate_dependencies(context.addrs);
 
+	versions = capture_collation_versions(context.addrs);
+
 	/* And record 'em */
 	recordMultipleDependencies(depender,
-							   context.addrs->refs, NULL,
+							   context.addrs->refs,
+							   versions,
 							   context.addrs->numrefs,
 							   behavior);
 
@@ -1464,6 +1484,7 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 {
 	find_expr_references_context context;
 	RangeTblEntry rte;
+	NameData *versions;
 
 	context.addrs = new_object_addresses();
 
@@ -1524,9 +1545,12 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 		free_object_addresses(self_addrs);
 	}
 
+	versions = capture_collation_versions(context.addrs);
+
 	/* Record the external dependencies */
 	recordMultipleDependencies(depender,
-							   context.addrs->refs, NULL,
+							   context.addrs->refs,
+							   versions,
 							   context.addrs->numrefs,
 							   behavior);
 
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 6781b00c6e6..2972706cf4c 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -23,6 +23,7 @@
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
 #include "catalog/partition.h"
+#include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_type.h"
@@ -32,6 +33,7 @@
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
+#include "utils/pg_locale.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
@@ -1420,3 +1422,44 @@ check_functional_grouping(Oid relid,
 
 	return false;
 }
+
+static NameData *
+constraint_check_collation_version(const ObjectAddress *otherObject,
+								   const NameData *version,
+								   void *userdata)
+{
+	const char *constraint_name = (const char *) userdata;
+	NameData	current_version;
+
+	/* We only care about dependencies on collations. */
+	if (otherObject->classId != CollationRelationId)
+		return NULL;
+
+	/* Compare with the current version. */
+	get_collation_version_for_oid(otherObject->objectId, &current_version);
+	if (strncmp(NameStr(*version),
+				NameStr(current_version),
+				sizeof(NameData)) != 0)
+		ereport(WARNING,
+				(errmsg("constraint \"%s\" depends on collation %u version \"%s\", but the current version is \"%s\"",
+						constraint_name,
+						otherObject->objectId,
+						NameStr(*version),
+						NameStr(current_version)),
+				 errdetail("The constraint may be corrupted due to changes in sort order."),
+				 errhint("Drop and recreate the constraint to avoid the risk of corruption.")));
+
+	return NULL;
+}
+
+void
+constraint_check_collation_versions(Oid oid, const char *constraint_name)
+{
+	ObjectAddress	object;
+
+	object.classId = ConstraintRelationId;
+	object.objectId = oid;
+	object.objectSubId = 0;
+	visitDependentObjects(&object, &constraint_check_collation_version,
+						  (void *) constraint_name);
+}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index b58d4f2af6d..c554704b771 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4056,6 +4056,10 @@ CheckConstraintFetch(Relation relation)
 		check[found].ccbin = MemoryContextStrdup(CacheMemoryContext, s);
 		pfree(s);
 
+		/* Generate warnings if dependent collation versions have moved. */
+		constraint_check_collation_versions(HeapTupleGetOid(htup),
+											NameStr(conform->conname));
+
 		found++;
 	}
 
diff --git a/src/include/catalog/pg_constraint.h b/src/include/catalog/pg_constraint.h
index 66b3f13f74a..2126f503d35 100644
--- a/src/include/catalog/pg_constraint.h
+++ b/src/include/catalog/pg_constraint.h
@@ -261,4 +261,7 @@ extern bool check_functional_grouping(Oid relid,
 						  List *grouping_columns,
 						  List **constraintDeps);
 
+void
+constraint_check_collation_versions(Oid oid, const char *constraint_name);
+
 #endif							/* PG_CONSTRAINT_H */
-- 
2.17.0

