0004-Track-collation-versions-for-indexes-v10.patch
application/octet-stream
Filename: 0004-Track-collation-versions-for-indexes-v10.patch
Type: application/octet-stream
Part: 3
Message:
Re: Collation versioning
Patch
Format: format-patch
Series: patch v10-0004
Subject: Track collation versions for indexes.
| File | + | − |
|---|---|---|
| doc/src/sgml/func.sgml | 1 | 1 |
| src/backend/catalog/dependency.c | 169 | 20 |
| src/backend/catalog/heap.c | 4 | 3 |
| src/backend/catalog/index.c | 181 | 10 |
| src/backend/catalog/pg_constraint.c | 1 | 1 |
| src/backend/catalog/pg_depend.c | 120 | 16 |
| src/backend/catalog/pg_type.c | 69 | 0 |
| src/backend/commands/collationcmds.c | 2 | 4 |
| src/backend/commands/vacuum.c | 31 | 0 |
| src/backend/optimizer/util/plancat.c | 9 | 0 |
| src/backend/utils/adt/pg_locale.c | 55 | 1 |
| src/backend/utils/cache/relcache.c | 2 | 0 |
| src/include/catalog/dependency.h | 15 | 7 |
| src/include/catalog/index.h | 2 | 0 |
| src/include/catalog/pg_type.h | 2 | 0 |
| src/include/utils/pg_locale.h | 1 | 0 |
| src/include/utils/rel.h | 1 | 0 |
| src/test/locale/.gitignore | 1 | 0 |
| src/test/locale/Makefile | 7 | 0 |
| src/test/locale/t/001_index.pl | 76 | 0 |
| src/test/Makefile | 3 | 2 |
| src/test/regress/expected/collate.icu.utf8.out | 130 | 0 |
| src/test/regress/expected/create_index.out | 6 | 2 |
| src/test/regress/sql/collate.icu.utf8.sql | 83 | 0 |
From 785675b6310cb342dfaa645674bc03456e78b879 Mon Sep 17 00:00:00 2001
From: Thomas Munro <thomas.munro@gmail.com>
Date: Tue, 28 May 2019 14:16:29 -0400
Subject: [PATCH 4/6] Track collation versions for indexes.
Record the current version of dependent collations in pg_depend when
creating or rebuilding an index. That version is checked against current
version whenever we call get_relation_info for an index or open the parent
table during non-full VACUUM or ANALYZE. Warn that the index may be corrupted
if the versions don't match.
A new flag is added in RelationData to specify that the check has already beed
done to avoid checking and reporting the message multiple time in a backend
lifetime.
Author: Thomas Munro, Julien Rouhaud
Reviewed-by:
Discussion: https://postgr.es/m/CAEepm%3D0uEQCpfq_%2BLYFBdArCe4Ot98t1aR4eYiYTe%3DyavQygiQ%40mail.gmail.com
---
doc/src/sgml/func.sgml | 2 +-
src/backend/catalog/dependency.c | 189 +++++++++++++++--
src/backend/catalog/heap.c | 7 +-
src/backend/catalog/index.c | 191 +++++++++++++++++-
src/backend/catalog/pg_constraint.c | 2 +-
src/backend/catalog/pg_depend.c | 136 +++++++++++--
src/backend/catalog/pg_type.c | 69 +++++++
src/backend/commands/collationcmds.c | 6 +-
src/backend/commands/vacuum.c | 31 +++
src/backend/optimizer/util/plancat.c | 9 +
src/backend/utils/adt/pg_locale.c | 56 ++++-
src/backend/utils/cache/relcache.c | 2 +
src/include/catalog/dependency.h | 22 +-
src/include/catalog/index.h | 2 +
src/include/catalog/pg_type.h | 2 +
src/include/utils/pg_locale.h | 1 +
src/include/utils/rel.h | 1 +
src/test/Makefile | 5 +-
src/test/locale/.gitignore | 1 +
src/test/locale/Makefile | 7 +
src/test/locale/t/001_index.pl | 76 +++++++
.../regress/expected/collate.icu.utf8.out | 130 ++++++++++++
src/test/regress/expected/create_index.out | 8 +-
src/test/regress/sql/collate.icu.utf8.sql | 83 ++++++++
24 files changed, 971 insertions(+), 67 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 44d82ca0b3..62b95cd52b 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -21103,7 +21103,7 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup());
<para>
<function>pg_collation_actual_version</function> returns the actual
version of the collation object as it is currently installed in the
- operating system.
+ operating system. An empty string is returned if the version is unknown.
</para>
<para>
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 78c31baa34..93f57cd633 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -77,6 +77,7 @@
#include "parser/parsetree.h"
#include "rewrite/rewriteRemove.h"
#include "storage/lmgr.h"
+#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
@@ -137,6 +138,9 @@ typedef struct
{
ObjectAddresses *addrs; /* addresses being accumulated */
List *rtables; /* list of rangetables to resolve Vars */
+ bool track_version; /* whether caller asked to track dependency
+ * versions */
+ NodeTag type; /* nodetag of the current node */
} find_expr_references_context;
/*
@@ -437,6 +441,80 @@ performMultipleDeletions(const ObjectAddresses *objects,
table_close(depRel, RowExclusiveLock);
}
+/*
+ * Call a function for all objects that depend on 'object'. If the function
+ * returns a non-NULL pointer to a new version string, update the version.
+ */
+void
+visitDependentObjects(const ObjectAddress *object,
+ VisitDependentObjectsFun callback,
+ void *userdata)
+{
+ Relation depRel;
+ ScanKeyData key[3];
+ SysScanDesc scan;
+ HeapTuple tup;
+ ObjectAddress otherObject;
+
+ ScanKeyInit(&key[0],
+ Anum_pg_depend_classid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(object->classId));
+ ScanKeyInit(&key[1],
+ Anum_pg_depend_objid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(object->objectId));
+ ScanKeyInit(&key[2],
+ Anum_pg_depend_objsubid,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(object->objectSubId));
+
+ depRel = table_open(DependRelationId, RowExclusiveLock);
+ scan = systable_beginscan(depRel, DependDependerIndexId, true,
+ NULL, 3, key);
+
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
+ char *cur_version, *new_version;
+ Datum depversion;
+ bool isnull;
+
+ otherObject.classId = foundDep->refclassid;
+ otherObject.objectId = foundDep->refobjid;
+ otherObject.objectSubId = foundDep->refobjsubid;
+
+ depversion = heap_getattr(tup, Anum_pg_depend_refobjversion,
+ RelationGetDescr(depRel), &isnull);
+
+ cur_version = isnull ? NULL : TextDatumGetCString(depversion);
+
+ new_version = callback(&otherObject, cur_version, userdata);
+ if (new_version)
+ {
+ Datum values[Natts_pg_depend];
+ bool nulls[Natts_pg_depend];
+ bool replaces[Natts_pg_depend];
+
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+ memset(replaces, false, sizeof(replaces));
+
+ values[Anum_pg_depend_refobjversion - 1] =
+ CStringGetTextDatum(new_version);
+ replaces[Anum_pg_depend_refobjversion - 1] = true;
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(depRel), values,
+ nulls, replaces);
+ CatalogTupleUpdate(depRel, &tup->t_self, tup);
+
+ heap_freetuple(tup);
+ }
+ }
+ systable_endscan(scan);
+ table_close(depRel, RowExclusiveLock);
+}
+
/*
* findDependentObjects - find all objects that depend on 'object'
*
@@ -1590,6 +1668,10 @@ recordDependencyOnExpr(const ObjectAddress *depender,
find_expr_references_context context;
context.addrs = new_object_addresses();
+ if (expr)
+ context.type = expr->type;
+ else
+ context.type = T_Invalid;
/* Set up interpretation for Vars at varlevelsup = 0 */
context.rtables = list_make1(rtable);
@@ -1602,9 +1684,10 @@ recordDependencyOnExpr(const ObjectAddress *depender,
/* And record 'em */
recordMultipleDependencies(depender,
- context.addrs->refs, NULL,
+ context.addrs->refs,
context.addrs->numrefs,
- behavior);
+ behavior,
+ false);
free_object_addresses(context.addrs);
}
@@ -1631,12 +1714,18 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId,
DependencyType behavior,
DependencyType self_behavior,
- bool reverse_self)
+ bool reverse_self,
+ bool track_version)
{
find_expr_references_context context;
RangeTblEntry rte;
context.addrs = new_object_addresses();
+ context.track_version = track_version;
+ if (expr)
+ context.type = expr->type;
+ else
+ context.type = T_Invalid;
/* We gin up a rather bogus rangetable list to handle Vars */
MemSet(&rte, 0, sizeof(rte));
@@ -1690,9 +1779,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
/* Record the self-dependencies with the appropriate direction */
if (!reverse_self)
recordMultipleDependencies(depender,
- self_addrs->refs, NULL,
+ self_addrs->refs,
self_addrs->numrefs,
- self_behavior);
+ self_behavior,
+ track_version);
else
{
/* Can't use recordMultipleDependencies, so do it the hard way */
@@ -1711,9 +1801,10 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
/* Record the external dependencies */
recordMultipleDependencies(depender,
- context.addrs->refs, NULL,
+ context.addrs->refs,
context.addrs->numrefs,
- behavior);
+ behavior,
+ track_version);
free_object_addresses(context.addrs);
}
@@ -1735,8 +1826,13 @@ static bool
find_expr_references_walker(Node *node,
find_expr_references_context *context)
{
+ NodeTag parent_type = context->type;
+
if (node == NULL)
return false;
+
+ context->type = node->type;
+
if (IsA(node, Var))
{
Var *var = (Var *) node;
@@ -1769,6 +1865,46 @@ find_expr_references_walker(Node *node,
/* If it's a plain relation, reference this column */
add_object_address(OCLASS_CLASS, rte->relid, var->varattno,
context->addrs);
+
+ /* Record collations from the type itself, or underlying in case of
+ * complex type. Note that if the direct parent is a CollateExpr
+ * node, there's no need to record the type underlying collation if
+ * any. A dependency already exists for the owning relation, and a
+ * change in the collation sort order wouldn't cause any harm as
+ * the collation isn't used at all in such case.
+ */
+ if (parent_type != T_CollateExpr)
+ {
+ /* type's collation if valid */
+ if (OidIsValid(var->varcollid))
+ {
+ add_object_address(OCLASS_COLLATION, var->varcollid, 0,
+ context->addrs);
+ }
+ /*
+ * otherwise, it may be a composite type having underlying
+ * collations */
+ else if (var->vartype >= FirstNormalObjectId)
+ {
+ List *collations = NIL;
+ ListCell *lc;
+
+ collations = GetTypeCollations(var->vartype, false);
+
+ foreach(lc, collations)
+ {
+ Oid coll = lfirst_oid(lc);
+
+ if (OidIsValid(coll) &&
+ (coll != DEFAULT_COLLATION_OID ||
+ context->track_version)
+ )
+ add_object_address(OCLASS_COLLATION,
+ lfirst_oid(lc), 0,
+ context->addrs);
+ }
+ }
+ }
}
/*
@@ -1793,11 +1929,13 @@ find_expr_references_walker(Node *node,
/*
* We must also depend on the constant's collation: it could be
* different from the datatype's, if a CollateExpr was const-folded to
- * a simple constant. However we can save work in the most common
- * case where the collation is "default", since we know that's pinned.
+ * a simple constant. However we can save work in the most common case
+ * where the collation is "default", since we know that's pinned, if
+ * the caller didn't ask to track dependency versions.
*/
if (OidIsValid(con->constcollid) &&
- con->constcollid != DEFAULT_COLLATION_OID)
+ (con->constcollid != DEFAULT_COLLATION_OID ||
+ context->track_version))
add_object_address(OCLASS_COLLATION, con->constcollid, 0,
context->addrs);
@@ -1887,7 +2025,8 @@ find_expr_references_walker(Node *node,
context->addrs);
/* and its collation, just as for Consts */
if (OidIsValid(param->paramcollid) &&
- param->paramcollid != DEFAULT_COLLATION_OID)
+ (param->paramcollid != DEFAULT_COLLATION_OID ||
+ context->track_version))
add_object_address(OCLASS_COLLATION, param->paramcollid, 0,
context->addrs);
}
@@ -1975,7 +2114,8 @@ find_expr_references_walker(Node *node,
context->addrs);
/* the collation might not be referenced anywhere else, either */
if (OidIsValid(fselect->resultcollid) &&
- fselect->resultcollid != DEFAULT_COLLATION_OID)
+ (fselect->resultcollid != DEFAULT_COLLATION_OID ||
+ context->track_version))
add_object_address(OCLASS_COLLATION, fselect->resultcollid, 0,
context->addrs);
}
@@ -2006,7 +2146,8 @@ find_expr_references_walker(Node *node,
context->addrs);
/* the collation might not be referenced anywhere else, either */
if (OidIsValid(relab->resultcollid) &&
- relab->resultcollid != DEFAULT_COLLATION_OID)
+ (relab->resultcollid != DEFAULT_COLLATION_OID ||
+ context->track_version))
add_object_address(OCLASS_COLLATION, relab->resultcollid, 0,
context->addrs);
}
@@ -2019,7 +2160,8 @@ find_expr_references_walker(Node *node,
context->addrs);
/* the collation might not be referenced anywhere else, either */
if (OidIsValid(iocoerce->resultcollid) &&
- iocoerce->resultcollid != DEFAULT_COLLATION_OID)
+ (iocoerce->resultcollid != DEFAULT_COLLATION_OID ||
+ context->track_version))
add_object_address(OCLASS_COLLATION, iocoerce->resultcollid, 0,
context->addrs);
}
@@ -2032,7 +2174,8 @@ find_expr_references_walker(Node *node,
context->addrs);
/* the collation might not be referenced anywhere else, either */
if (OidIsValid(acoerce->resultcollid) &&
- acoerce->resultcollid != DEFAULT_COLLATION_OID)
+ (acoerce->resultcollid != DEFAULT_COLLATION_OID ||
+ context->track_version))
add_object_address(OCLASS_COLLATION, acoerce->resultcollid, 0,
context->addrs);
/* fall through to examine arguments */
@@ -2121,7 +2264,8 @@ find_expr_references_walker(Node *node,
add_object_address(OCLASS_PROC, wc->endInRangeFunc, 0,
context->addrs);
if (OidIsValid(wc->inRangeColl) &&
- wc->inRangeColl != DEFAULT_COLLATION_OID)
+ (wc->inRangeColl != DEFAULT_COLLATION_OID ||
+ context->track_version))
add_object_address(OCLASS_COLLATION, wc->inRangeColl, 0,
context->addrs);
/* fall through to examine substructure */
@@ -2266,7 +2410,9 @@ find_expr_references_walker(Node *node,
{
Oid collid = lfirst_oid(ct);
- if (OidIsValid(collid) && collid != DEFAULT_COLLATION_OID)
+ if (OidIsValid(collid) &&
+ (collid != DEFAULT_COLLATION_OID ||
+ context->track_version))
add_object_address(OCLASS_COLLATION, collid, 0,
context->addrs);
}
@@ -2288,7 +2434,9 @@ find_expr_references_walker(Node *node,
{
Oid collid = lfirst_oid(ct);
- if (OidIsValid(collid) && collid != DEFAULT_COLLATION_OID)
+ if (OidIsValid(collid) &&
+ (collid != DEFAULT_COLLATION_OID ||
+ context->track_version))
add_object_address(OCLASS_COLLATION, collid, 0,
context->addrs);
}
@@ -2684,8 +2832,9 @@ record_object_address_dependencies(const ObjectAddress *depender,
{
eliminate_duplicate_dependencies(referenced);
recordMultipleDependencies(depender,
- referenced->refs, NULL, referenced->numrefs,
- behavior);
+ referenced->refs, referenced->numrefs,
+ behavior,
+ false);
}
/*
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e31478bf91..46e9d74a97 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2304,7 +2304,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
*/
recordDependencyOnSingleRelExpr(&colobject, expr, RelationGetRelid(rel),
DEPENDENCY_AUTO,
- DEPENDENCY_AUTO, false);
+ DEPENDENCY_AUTO, false, false);
}
else
{
@@ -2314,7 +2314,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
*/
recordDependencyOnSingleRelExpr(&defobject, expr, RelationGetRelid(rel),
DEPENDENCY_NORMAL,
- DEPENDENCY_NORMAL, false);
+ DEPENDENCY_NORMAL, false, false);
}
/*
@@ -3638,7 +3638,8 @@ StorePartitionKey(Relation rel,
RelationGetRelid(rel),
DEPENDENCY_NORMAL,
DEPENDENCY_INTERNAL,
- true /* reverse the self-deps */ );
+ true /* reverse the self-deps */,
+ false /* don't track versions */);
/*
* We must invalidate the relcache so that the next
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 8880586c37..c6c51e0a48 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "catalog/storage.h"
+#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
@@ -74,6 +75,7 @@
#include "utils/guc.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
+#include "utils/pg_locale.h"
#include "utils/memutils.h"
#include "utils/pg_rusage.h"
#include "utils/snapmgr.h"
@@ -117,6 +119,7 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
bool immediate,
bool isvalid,
bool isready);
+static bool index_depends_stable_coll_order(Oid amoid);
static void index_update_stats(Relation rel,
bool hasindex,
double reltuples);
@@ -1025,6 +1028,10 @@ index_create(Relation heapRelation,
{
ObjectAddress myself,
referenced;
+ ListCell *lc;
+ List *colls = NIL,
+ *determ_colls = NIL,
+ *nondeterm_colls = NIL;
myself.classId = RelationRelationId;
myself.objectId = indexRelationId;
@@ -1115,21 +1122,75 @@ index_create(Relation heapRelation,
recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_SEC);
}
- /* Store dependency on collations */
- /* The default collation is pinned, so don't bother recording it */
+ /* First, get all dependencies on collations for all index keys. */
for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
{
- if (OidIsValid(collationObjectId[i]) &&
- collationObjectId[i] != DEFAULT_COLLATION_OID)
+ Oid colloid = collationObjectId[i];
+
+ if (OidIsValid(colloid))
+ colls = lappend_oid(colls, colloid);
+ else
{
- referenced.classId = CollationRelationId;
- referenced.objectId = collationObjectId[i];
- referenced.objectSubId = 0;
+ Form_pg_attribute att = TupleDescAttr(indexTupDesc, i);
- recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ Assert(i < indexTupDesc->natts);
+
+ colls = list_concat(colls,
+ GetTypeCollations(att->atttypid, false));
}
}
+ /*
+ * Then split the dependencies on whether they're deterministic or not,
+ * removing any duplicates.
+ */
+ foreach(lc, colls)
+ {
+ Oid c = lfirst_oid(lc);
+
+ if (!OidIsValid(c))
+ continue;
+
+ if (get_collation_isdeterministic(c))
+ determ_colls = list_append_unique_oid(determ_colls, c);
+ else
+ nondeterm_colls = list_append_unique_oid(nondeterm_colls, c);
+ }
+
+ /*
+ * For deterministic transaction, only track the version if the AM
+ * relies on a stable ordering.
+ */
+ if (determ_colls)
+ {
+ bool track_version;
+
+ track_version = index_depends_stable_coll_order(indexInfo->ii_Am);
+
+ recordDependencyOnCollations(&myself, determ_colls, track_version);
+ /*
+ * Advance the command counter so that later calls to
+ * recordMultipleDependencies calls can see the newly-entered
+ * pg_depend catalog tuples for the index.
+ */
+ CommandCounterIncrement();
+ }
+
+ /*
+ * We always record the version for dependency on non-deterministic
+ * collations.
+ */
+ if (nondeterm_colls)
+ {
+ recordDependencyOnCollations(&myself, nondeterm_colls, true);
+ /*
+ * Advance the command counter so that later calls to
+ * recordMultipleDependencies calls can see the newly-entered
+ * pg_depend catalog tuples for the index.
+ */
+ CommandCounterIncrement();
+ }
+
/* Store dependency on operator classes */
for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++)
{
@@ -1143,21 +1204,29 @@ index_create(Relation heapRelation,
/* Store dependencies on anything mentioned in index expressions */
if (indexInfo->ii_Expressions)
{
+ /* recordDependencyOnSingleRelExpr get rid of duplicate entries */
recordDependencyOnSingleRelExpr(&myself,
(Node *) indexInfo->ii_Expressions,
heapRelationId,
DEPENDENCY_NORMAL,
- DEPENDENCY_AUTO, false);
+ DEPENDENCY_AUTO, false, true);
+ /*
+ * Advance the command counter so that later
+ * recordMultipleDependencies calls can see the newly-entered
+ * pg_depend catalog tuples for the index.
+ */
+ CommandCounterIncrement();
}
/* Store dependencies on anything mentioned in predicate */
if (indexInfo->ii_Predicate)
{
+ /* recordDependencyOnSingleRelExpr get rid of duplicate entries */
recordDependencyOnSingleRelExpr(&myself,
(Node *) indexInfo->ii_Predicate,
heapRelationId,
DEPENDENCY_NORMAL,
- DEPENDENCY_AUTO, false);
+ DEPENDENCY_AUTO, false, true);
}
}
else
@@ -1229,6 +1298,94 @@ index_create(Relation heapRelation,
return indexRelationId;
}
+static char *
+index_check_collation_version(const ObjectAddress *otherObject,
+ const char *version,
+ void *userdata)
+{
+ Oid relid = *(Oid *) userdata;
+ char *current_version;
+
+ /* We only care about dependencies on collations. */
+ if (otherObject->classId != CollationRelationId)
+ return NULL;
+
+ /* Compare with the current version. */
+ current_version = get_collation_version_for_oid(otherObject->objectId);
+
+ /* XXX should we warn about "disappearing" versions? */
+ if (current_version)
+ {
+ /*
+ * We now support versioning for the underlying collation library on
+ * this system, or previous version is unknown.
+ */
+ if (!version || (strcmp(version, "") == 0 && strcmp(current_version,
+ "") != 0))
+ {
+ ereport(WARNING,
+ (errmsg("index \"%s\" depends on collation \"%s\" with an unknown version, and the current version is \"%s\"",
+ get_rel_name(relid),
+ get_collation_name(otherObject->objectId),
+ current_version),
+ errdetail("The index may be corrupted due to changes in sort order."),
+ errhint("REINDEX to avoid the risk of corruption.")));
+ }
+ else if (strcmp(current_version, version) != 0)
+ {
+ ereport(WARNING,
+ (errmsg("index \"%s\" depends on collation \"%s\" version \"%s\", but the current version is \"%s\"",
+ get_rel_name(relid),
+ get_collation_name(otherObject->objectId),
+ version,
+ current_version),
+ errdetail("The index may be corrupted due to changes in sort order."),
+ errhint("REINDEX to avoid the risk of corruption.")));
+ }
+ }
+
+ return NULL;
+}
+
+void
+index_check_collation_versions(Oid relid)
+{
+ ObjectAddress object;
+
+ object.classId = RelationRelationId;
+ object.objectId = relid;
+ object.objectSubId = 0;
+ visitDependentObjects(&object, &index_check_collation_version, &relid);
+}
+
+static char *
+index_update_collation_version(const ObjectAddress *otherObject,
+ const char *version,
+ void *userdata)
+{
+ char *current_version = (char *) userdata;
+
+ /* We only care about dependencies on collations. */
+ if (otherObject->classId != CollationRelationId)
+ return NULL;
+
+ current_version = get_collation_version_for_oid(otherObject->objectId);
+ return current_version;
+}
+
+static void
+index_update_collation_versions(Oid relid)
+{
+ ObjectAddress object;
+ NameData current_version;
+
+ object.classId = RelationRelationId;
+ object.objectId = relid;
+ object.objectSubId = 0;
+ visitDependentObjects(&object, &index_update_collation_version,
+ ¤t_version);
+}
+
/*
* index_concurrently_create_copy
*
@@ -2628,6 +2785,17 @@ FormIndexDatum(IndexInfo *indexInfo,
elog(ERROR, "wrong number of index expressions");
}
+/*
+ * Returns whether the given index access method depend on a stable collation
+ * order.
+ */
+static bool
+index_depends_stable_coll_order(Oid amoid)
+{
+ return (amoid != HASH_AM_OID &&
+ strcmp(get_am_name(amoid), "bloom") != 0);
+}
+
/*
* index_update_stats --- update pg_class entry after CREATE INDEX or REINDEX
@@ -3605,6 +3773,9 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
/* Close rels, but keep locks */
index_close(iRel, NoLock);
table_close(heapRelation, NoLock);
+
+ /* Record the current versions of all depended-on collations. */
+ index_update_collation_versions(indexId);
}
/*
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 3d2b1cc911..0eaefbd032 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -360,7 +360,7 @@ CreateConstraintEntry(const char *constraintName,
*/
recordDependencyOnSingleRelExpr(&conobject, conExpr, relId,
DEPENDENCY_NORMAL,
- DEPENDENCY_NORMAL, false);
+ DEPENDENCY_NORMAL, false, true);
}
/* Post creation hook for new constraint */
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 7fdbdf0ae8..7e087fefc4 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -19,16 +19,21 @@
#include "access/table.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
+#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_extension.h"
#include "commands/extension.h"
#include "miscadmin.h"
+#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
+#include "utils/pg_locale.h"
#include "utils/rel.h"
+static bool dependencyExists(const ObjectAddress *depender,
+ const ObjectAddress *referenced);
static bool isObjectPinned(const ObjectAddress *object, Relation rel);
@@ -44,34 +49,47 @@ recordDependencyOn(const ObjectAddress *depender,
const ObjectAddress *referenced,
DependencyType behavior)
{
- recordMultipleDependencies(depender, referenced, NULL, 1, behavior);
+ recordMultipleDependencies(depender, referenced, 1, behavior, false);
}
/*
- * As recordDependencyOn(), but also capture a version string so that changes
- * in the referenced object can be detected. The meaning of the version
- * string depends on the referenced object. Currently it is used for
- * detecting changes in collation versions.
+ * Given a list of collations, record a dependency on its underlying collation
+ * version.
*/
-void
-recordDependencyOnVersion(const ObjectAddress *depender,
- const ObjectAddress *referenced,
- const NameData *version,
- DependencyType behavior)
+void recordDependencyOnCollations(ObjectAddress *myself,
+ List *collations,
+ bool record_version)
{
- recordMultipleDependencies(depender, referenced, version, 1, behavior);
+ ListCell *lc;
+
+ foreach(lc, collations)
+ {
+ ObjectAddress referenced;
+
+ referenced.classId = CollationRelationId;
+ referenced.objectId = lfirst_oid(lc);
+ referenced.objectSubId = 0;
+
+ recordMultipleDependencies(myself, &referenced, 1,
+ DEPENDENCY_NORMAL, record_version);
+ }
}
/*
* Record multiple dependencies (of the same kind) for a single dependent
* object. This has a little less overhead than recording each separately.
+ *
+ * If track_version is true, then a record could be added even if the referenced
+ * dependency is pinned, and the dependency version will be retrieved according
+ * to the referenced object kind.
+ * For now, only collation version is supported.
*/
void
recordMultipleDependencies(const ObjectAddress *depender,
const ObjectAddress *referenced,
- const NameData *version,
int nreferenced,
- DependencyType behavior)
+ DependencyType behavior,
+ bool track_version)
{
Relation dependDesc;
CatalogIndexState indstate;
@@ -79,6 +97,7 @@ recordMultipleDependencies(const ObjectAddress *depender,
int i;
bool nulls[Natts_pg_depend];
Datum values[Natts_pg_depend];
+ char *version = NULL;
if (nreferenced <= 0)
return; /* nothing to do */
@@ -97,12 +116,49 @@ recordMultipleDependencies(const ObjectAddress *depender,
for (i = 0; i < nreferenced; i++, referenced++)
{
+ bool ignore_systempin = false;
+
+ if (track_version)
+ {
+ /* Only dependency on a collation is supported. */
+ if (referenced->classId == CollationRelationId)
+ {
+ /* C and POSIX collations don't require tracking the version */
+ if (referenced->objectId == C_COLLATION_OID ||
+ referenced->objectId == POSIX_COLLATION_OID)
+ continue;
+
+ /*
+ * We don't want to record redundant depedencies that are used
+ * to track versions to avoid redundant warnings in case of
+ * non-matching versions when those are checked. Note that
+ * callers have to take care of removing duplicated entries and
+ * calling CommandCounterIncrement() if the dependencies are
+ * registered in multiple calls.
+ */
+ if (dependencyExists(depender, referenced))
+ continue;
+
+ /*
+ * Default collation is pinned, so we need to force recording
+ * the dependency to store the version.
+ */
+ if (referenced->objectId == DEFAULT_COLLATION_OID)
+ ignore_systempin = true;
+ version = get_collation_version_for_oid(referenced->objectId);
+ Assert(version);
+ }
+ }
+ else
+ Assert(!version);
+
/*
* If the referenced object is pinned by the system, there's no real
- * need to record dependencies on it. This saves lots of space in
- * pg_depend, so it's worth the time taken to check.
+ * need to record dependencies on it, unless we need to record a
+ * version. This saves lots of space in pg_depend, so it's worth the
+ * time taken to check.
*/
- if (!isObjectPinned(referenced, dependDesc))
+ if (ignore_systempin || !isObjectPinned(referenced, dependDesc))
{
/*
* Record the Dependency. Note we don't bother to check for
@@ -559,6 +615,54 @@ changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
return count;
}
+/* dependencyExists()
+ *
+ * Test if a record exists for the given depender and referenceds addresses.
+ */
+static bool dependencyExists(const ObjectAddress *depender,
+ const ObjectAddress *referenced)
+{
+ Relation depRel;
+ ScanKeyData key[3];
+ SysScanDesc scan;
+ HeapTuple tup;
+ bool ret = false;
+
+ ScanKeyInit(&key[0],
+ Anum_pg_depend_classid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(depender->classId));
+ ScanKeyInit(&key[1],
+ Anum_pg_depend_objid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(depender->objectId));
+ ScanKeyInit(&key[2],
+ Anum_pg_depend_objsubid,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(depender->objectSubId));
+
+ depRel = table_open(DependRelationId, RowExclusiveLock);
+ scan = systable_beginscan(depRel, DependDependerIndexId, true,
+ NULL, 3, key);
+
+ while (HeapTupleIsValid(tup = systable_getnext(scan)))
+ {
+ Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
+
+ if (foundDep->refclassid == referenced->classId &&
+ foundDep->refobjid == referenced->objectId &&
+ foundDep->refobjsubid == referenced->objectSubId)
+ {
+ ret = true;
+ break;
+ }
+ }
+ systable_endscan(scan);
+ table_close(depRel, RowExclusiveLock);
+
+ return ret;
+}
+
/*
* isObjectPinned()
*
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 8d7572da51..6e91518694 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -15,6 +15,7 @@
#include "postgres.h"
#include "access/htup_details.h"
+#include "access/relation.h"
#include "access/table.h"
#include "access/xact.h"
#include "catalog/binary_upgrade.h"
@@ -511,6 +512,74 @@ TypeCreate(Oid newTypeOid,
return address;
}
+/* Get a list of all distinct collations oid that the given type depends on. */
+List *
+GetTypeCollations(Oid typeoid, bool non_deterministic_only)
+{
+ List *result = NIL;
+ HeapTuple tuple;
+ Form_pg_type typeTup;
+
+ tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typeoid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for type %u", typeoid);
+ typeTup = (Form_pg_type) GETSTRUCT(tuple);
+
+ if (OidIsValid(typeTup->typcollation))
+ {
+ if (!non_deterministic_only ||
+ !get_collation_isdeterministic(typeTup->typcollation))
+ result = list_append_unique_oid(result, typeTup->typcollation);
+ }
+ else if (typeTup->typtype == TYPTYPE_COMPOSITE)
+ {
+ Relation rel = relation_open(typeTup->typrelid, AccessShareLock);
+ TupleDesc desc = RelationGetDescr(rel);
+
+ for (int i = 0; i < RelationGetNumberOfAttributes(rel); i++)
+ {
+ Form_pg_attribute att = TupleDescAttr(desc, i);
+
+ if (OidIsValid(att->attcollation))
+ {
+ if (!non_deterministic_only ||
+ !get_collation_isdeterministic(att->attcollation))
+ result = list_append_unique_oid(result, att->attcollation);
+ }
+ else
+ result = list_concat_unique_oid(result,
+ GetTypeCollations(att->atttypid,
+ non_deterministic_only));
+ }
+
+ relation_close(rel, NoLock);
+ }
+ else if (typeTup->typtype == TYPTYPE_DOMAIN)
+ {
+ Assert(OidIsValid(typeTup->typbasetype));
+
+ result = list_concat_unique_oid(result,
+ GetTypeCollations(typeTup->typbasetype,
+ non_deterministic_only));
+ }
+ else if (typeTup->typtype == TYPTYPE_RANGE)
+ {
+ Oid rangeid = get_range_subtype(typeTup->oid);
+
+ Assert(OidIsValid(rangeid));
+
+ result = list_concat_unique_oid(result,
+ GetTypeCollations(rangeid, non_deterministic_only));
+ }
+ else if (OidIsValid(typeTup->typelem))
+ result = list_concat_unique_oid(result,
+ GetTypeCollations(typeTup->typelem, non_deterministic_only));
+
+ ReleaseSysCache(tuple);
+
+ return result;
+}
+
/*
* GenerateTypeDependencies: build the dependencies needed for a type
*
diff --git a/src/backend/commands/collationcmds.c b/src/backend/commands/collationcmds.c
index 493aa21a14..58c237b2ee 100644
--- a/src/backend/commands/collationcmds.c
+++ b/src/backend/commands/collationcmds.c
@@ -289,11 +289,9 @@ pg_collation_actual_version(PG_FUNCTION_ARGS)
ReleaseSysCache(tp);
version = get_collation_actual_version(collprovider, collcollate);
+ Assert(version);
- if (version)
- PG_RETURN_TEXT_P(cstring_to_text(version));
- else
- PG_RETURN_NULL();
+ PG_RETURN_TEXT_P(cstring_to_text(version));
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17bf4..2b7de111cd 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -31,6 +31,8 @@
#include "access/tableam.h"
#include "access/transam.h"
#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "catalog/index.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
@@ -634,6 +636,35 @@ vacuum_open_relation(Oid relid, RangeVar *relation, int options,
rel_lock = false;
}
+ /*
+ * Perform version sanity checks on the relation underlying indexes if
+ * it's not a VACUUM FULL
+ */
+ if (!(options & VACOPT_FULL) && onerel && !IsSystemRelation(onerel) &&
+ onerel->rd_rel->relhasindex)
+ {
+ List *indexoidlist;
+ ListCell *l;
+
+ indexoidlist = RelationGetIndexList(onerel);
+ foreach(l, indexoidlist)
+ {
+ Oid indexoid = lfirst_oid(l);
+ Relation indexRelation;
+
+ indexRelation = index_open(indexoid, AccessShareLock);
+
+ /* Warn if any dependent collations' versions have moved. */
+ if (!indexRelation->rd_version_checked)
+ {
+ index_check_collation_versions(indexoid);
+ indexRelation->rd_version_checked = true;
+ }
+
+ index_close(indexRelation, NoLock);
+ }
+ }
+
/* if relation is opened, leave */
if (onerel)
return onerel;
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5ab8b..f2fc427fc8 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
+#include "catalog/index.h"
#include "catalog/pg_am.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_statistic_ext.h"
@@ -198,6 +199,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
indexRelation = index_open(indexoid, lmode);
index = indexRelation->rd_index;
+ /* Warn if any dependent collations' versions have moved. */
+ if (!IsSystemRelation(relation) &&
+ !indexRelation->rd_version_checked)
+ {
+ index_check_collation_versions(indexoid);
+ indexRelation->rd_version_checked = true;
+ }
+
/*
* Ignore invalid indexes, since they can't safely be used for
* queries. Note that this is OK because the data structure we
diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index 597c1241f9..a8bc5c363b 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"
@@ -1462,6 +1464,10 @@ pg_newlocale_from_collation(Oid collid)
* A particular provider must always either return a non-NULL string or return
* NULL (if it doesn't support versions). It must not return NULL for some
* collcollate and not NULL for others.
+ *
+ * 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_actual_version(char collprovider, const char *collcollate)
@@ -1498,9 +1504,57 @@ get_collation_actual_version(char collprovider, const char *collcollate)
#endif
}
- return collversion;
+ if (!collversion)
+ return "";
+ else
+ return collversion;
}
+/*
+ * Get provider-specific collation version string given a collation OID.
+ *
+ * As this version is used in index dependency tracking, an empty string is
+ * returned when the version is unknown, to distinguish from dependencies where
+ * the version should not be tracked, represented by a NULL version.
+ */
+char *
+get_collation_version_for_oid(Oid oid)
+{
+ HeapTuple tp;
+ char *version = NULL;
+
+ Assert(oid != C_COLLATION_OID && oid != POSIX_COLLATION_OID);
+
+ if (oid == DEFAULT_COLLATION_OID)
+ {
+ Form_pg_database dbform;
+
+ tp = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for database %u", MyDatabaseId);
+ dbform = (Form_pg_database) GETSTRUCT(tp);
+ version = get_collation_actual_version(COLLPROVIDER_LIBC,
+ NameStr(dbform->datcollate));
+ }
+ else
+ {
+ Form_pg_collation collform;
+
+ tp = SearchSysCache1(COLLOID, ObjectIdGetDatum(oid));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup failed for collation %u", oid);
+ collform = (Form_pg_collation) GETSTRUCT(tp);
+ version = get_collation_actual_version(collform->collprovider,
+ NameStr(collform->collcollate));
+ }
+
+ ReleaseSysCache(tp);
+
+ if (!version)
+ return "";
+ else
+ return version;
+}
#ifdef USE_ICU
/*
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index ff70326474..bdf50ffe89 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -41,6 +41,7 @@
#include "access/xact.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
+#include "catalog/index.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/pg_am.h"
@@ -5623,6 +5624,7 @@ load_relcache_init_file(bool shared)
rel->rd_idattr = NULL;
rel->rd_pubactions = NULL;
rel->rd_statvalid = false;
+ rel->rd_version_checked = false;
rel->rd_statlist = NIL;
rel->rd_fkeyvalid = false;
rel->rd_fkeylist = NIL;
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 77cf0612ed..8750bfc36f 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -156,7 +156,8 @@ extern void recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
Node *expr, Oid relId,
DependencyType behavior,
DependencyType self_behavior,
- bool reverse_self);
+ bool reverse_self,
+ bool track_version);
extern ObjectClass getObjectClass(const ObjectAddress *object);
@@ -176,22 +177,29 @@ extern void sort_object_addresses(ObjectAddresses *addrs);
extern void free_object_addresses(ObjectAddresses *addrs);
+typedef char *(*VisitDependentObjectsFun) (const ObjectAddress *otherObject,
+ const char *version,
+ void *userdata);
+
+extern void visitDependentObjects(const ObjectAddress *object,
+ VisitDependentObjectsFun callback,
+ void *userdata);
+
/* in pg_depend.c */
extern void recordDependencyOn(const ObjectAddress *depender,
const ObjectAddress *referenced,
DependencyType behavior);
-extern void recordDependencyOnVersion(const ObjectAddress *depender,
- const ObjectAddress *referenced,
- const NameData *version,
- DependencyType behavior);
+extern void recordDependencyOnCollations(ObjectAddress *myself,
+ List *collations,
+ bool record_version);
extern void recordMultipleDependencies(const ObjectAddress *depender,
const ObjectAddress *referenced,
- const NameData *version,
int nreferenced,
- DependencyType behavior);
+ DependencyType behavior,
+ bool track_version);
extern void recordDependencyOnCurrentExtension(const ObjectAddress *object,
bool isReplace);
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index a2890c1314..c619d02465 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -121,6 +121,8 @@ extern void FormIndexDatum(IndexInfo *indexInfo,
Datum *values,
bool *isnull);
+extern void index_check_collation_versions(Oid relid);
+
extern void index_build(Relation heapRelation,
Relation indexRelation,
IndexInfo *indexInfo,
diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h
index e1a5ab3df3..2bf6f868a5 100644
--- a/src/include/catalog/pg_type.h
+++ b/src/include/catalog/pg_type.h
@@ -335,6 +335,8 @@ extern void GenerateTypeDependencies(Oid typeObjectId,
bool isDependentType,
bool rebuild);
+extern List *GetTypeCollations(Oid typeObjectid, bool non_deterministic_only);
+
extern void RenameTypeInternal(Oid typeOid, const char *newTypeName,
Oid typeNamespace);
diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h
index 9cb7d91ddf..aa06aca90a 100644
--- a/src/include/utils/pg_locale.h
+++ b/src/include/utils/pg_locale.h
@@ -104,6 +104,7 @@ typedef struct pg_locale_struct *pg_locale_t;
extern pg_locale_t pg_newlocale_from_collation(Oid collid);
extern char *get_collation_actual_version(char collprovider, const char *collcollate);
+extern char *get_collation_version_for_oid(Oid collid);
#ifdef USE_ICU
extern int32_t icu_to_uchar(UChar **buff_uchar, const char *buff, size_t nbytes);
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index 44ed04dd3f..3656ea94e8 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -63,6 +63,7 @@ typedef struct RelationData
bool rd_indexvalid; /* is rd_indexlist valid? (also rd_pkindex and
* rd_replidindex) */
bool rd_statvalid; /* is rd_statlist valid? */
+ bool rd_version_checked; /* has version check being done yet? */
/*
* rd_createSubid is the ID of the highest subtransaction the rel has
diff --git a/src/test/Makefile b/src/test/Makefile
index efb206aa75..4ef0d11702 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -12,7 +12,8 @@ subdir = src/test
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
-SUBDIRS = perl regress isolation modules authentication recovery subscription
+SUBDIRS = perl regress isolation modules authentication recovery subscription \
+ locale
# Test suites that are not safe by default but can be run if selected
# by the user via the whitespace-separated list in variable
@@ -37,7 +38,7 @@ endif
# clean" etc to recurse into them. (We must filter out those that we
# have conditionally included into SUBDIRS above, else there will be
# make confusion.)
-ALWAYS_SUBDIRS = $(filter-out $(SUBDIRS),examples kerberos ldap locale thread ssl)
+ALWAYS_SUBDIRS = $(filter-out $(SUBDIRS),examples kerberos ldap thread ssl)
# We want to recurse to all subdirs for all standard targets, except that
# installcheck and install should not recurse into the subdirectory "modules".
diff --git a/src/test/locale/.gitignore b/src/test/locale/.gitignore
index 620d3df425..64e1bf2a80 100644
--- a/src/test/locale/.gitignore
+++ b/src/test/locale/.gitignore
@@ -1 +1,2 @@
/test-ctype
+/tmp_check/
diff --git a/src/test/locale/Makefile b/src/test/locale/Makefile
index 22a45b65f2..73495cf16b 100644
--- a/src/test/locale/Makefile
+++ b/src/test/locale/Makefile
@@ -4,6 +4,7 @@ subdir = src/test/locale
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
+export with_icu
PROGS = test-ctype
DIRS = de_DE.ISO8859-1 gr_GR.ISO8859-7 koi8-r koi8-to-win1251
@@ -19,3 +20,9 @@ clean distclean maintainer-clean:
# These behave like installcheck targets.
check-%: all
@$(MAKE) -C `echo $@ | sed 's/^check-//'` test
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
diff --git a/src/test/locale/t/001_index.pl b/src/test/locale/t/001_index.pl
new file mode 100644
index 0000000000..468fbb63b6
--- /dev/null
+++ b/src/test/locale/t/001_index.pl
@@ -0,0 +1,76 @@
+use strict;
+use warnings;
+
+use Config;
+use PostgresNode;
+use TestLib;
+use Test::More;
+
+if ($ENV{with_icu} eq 'yes')
+{
+ plan tests => 12;
+}
+else
+{
+ plan skip_all => 'ICU not supported by this build';
+}
+
+#### Set up the server
+
+note "setting up data directory";
+my $node = get_new_node('main');
+$node->init;
+
+$ENV{PGHOST} = $node->host;
+$ENV{PGPORT} = $node->port;
+$node->start;
+
+sub test_index
+{
+ my ($err_like, $err_comm) = @_;
+
+ my ($ret, $out, $err) = $node->psql('postgres',
+ 'SET enable_seqscan = 0;'
+ . "EXPLAIN SELECT val FROM icu1 WHERE val = '0'");
+
+ is($ret, 0, 'EXPLAIN should succeed.');
+ like($out, qr/icu1_fr/, 'Index icu1_fr should be used.');
+ like($err, $err_like, $err_comm);
+}
+
+$node->safe_psql('postgres',
+ 'CREATE TABLE icu1(val text);'
+ . 'INSERT INTO icu1 SELECT i::text FROM generate_series(1, 10000) i;'
+ . 'CREATE INDEX icu1_fr ON icu1 (val COLLATE "fr-x-icu");');
+$node->safe_psql('postgres', 'VACUUM ANALYZE icu1;');
+
+test_index(qr/^$/, 'No warning should be raised');
+
+# Simulate different collation version
+$node->safe_psql('postgres',
+ "UPDATE pg_depend SET refobjversion = 'not_a_version'"
+ . " WHERE refobjversion IS NOT NULL"
+ . " AND objid::regclass::text = 'icu1_fr';");
+
+test_index(qr/index "icu1_fr" depends on collation "fr-x-icu" version "not_a_version", but the current version is/,
+ 'Different collation version warning should be raised.');
+
+# Simulate unknown collation version
+$node->safe_psql('postgres',
+ "UPDATE pg_depend SET refobjversion = ''"
+ . " WHERE refobjversion IS NOT NULL"
+ . " AND objid::regclass::text = 'icu1_fr';");
+
+test_index(qr/index "icu1_fr" depends on collation "fr-x-icu" with an unknown version, and the current version is/,
+ 'Unknown collation version warning should be raised.');
+
+# Simulate previously unhandled collation versioning
+$node->safe_psql('postgres',
+ "UPDATE pg_depend SET refobjversion = NULL"
+ . " WHERE refobjversion IS NOT NULL"
+ . " AND objid::regclass::text = 'icu1_fr';");
+
+test_index(qr/index "icu1_fr" depends on collation "fr-x-icu" with an unknown version, and the current version is/,
+ 'Unknown collation version warning should be raised.');
+
+$node->stop;
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.out
index 60d9263a2f..09512c0f66 100644
--- a/src/test/regress/expected/collate.icu.utf8.out
+++ b/src/test/regress/expected/collate.icu.utf8.out
@@ -1897,6 +1897,136 @@ SELECT (SELECT count(*) FROM test33_0) <> (SELECT count(*) FROM test33_1);
t
(1 row)
+-- collation versioning support
+CREATE TYPE t_en_fr AS (fr text COLLATE "fr-x-icu", en text COLLATE "en-x-icu");
+CREATE DOMAIN d_en_fr AS t_en_fr;
+CREATE DOMAIN d_es AS text COLLATE "es-x-icu";
+CREATE TYPE t_en_fr_ga AS (en_fr t_en_fr, ga text COLLATE "ga-x-icu");
+CREATE DOMAIN d_en_fr_ga AS t_en_fr_ga;
+CREATE TYPE t_custom AS (meh text, meh2 text);
+CREATE DOMAIN d_custom AS t_custom;
+CREATE COLLATION custom (
+ LOCALE = 'fr-x-icu', PROVIDER = 'icu'
+);
+CREATE TYPE myrange AS range (subtype = text);
+CREATE TYPE myrange_en_fr_ga AS range(subtype = t_en_fr_ga);
+CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
+CREATE TABLE collate_test (
+ id integer,
+ t_en_fr t_en_fr,
+ d_en_fr d_en_fr,
+ d_es d_es,
+ t_en_fr_ga t_en_fr_ga,
+ d_en_fr_ga d_en_fr_ga,
+ d_en_fr_ga_arr d_en_fr_ga[],
+ myrange myrange,
+ myrange_en_fr_ga myrange_en_fr_ga,
+ mood mood
+);
+CREATE INDEX icuidx01_t_en_fr__d_es ON collate_test (t_en_fr, d_es);
+CREATE INDEX icuidx02_d_en_fr ON collate_test (d_en_fr);
+CREATE INDEX icuidx03_t_en_fr_ga ON collate_test (t_en_fr_ga);
+CREATE INDEX icuidx04_d_en_fr_ga ON collate_test (d_en_fr_ga);
+CREATE INDEX icuidx05_d_en_fr_ga_arr ON collate_test (d_en_fr_ga_arr);
+CREATE INDEX icuidx06_d_en_fr_ga ON collate_test(id) WHERE (d_en_fr_ga).en_fr.fr = 'foo';
+CREATE INDEX icuidx07_d_en_fr_ga ON collate_test(id) WHERE (d_en_fr_ga).ga = 'foo';
+CREATE INDEX icuidx08_d_en_fr_ga ON collate_test(id) WHERE (t_en_fr_ga) = ('foo', 'bar', 'baz');
+CREATE INDEX icuidx09_d_en_fr_ga ON collate_test(id) WHERE (d_en_fr_ga) = ('foo', 'bar', 'baz');
+CREATE INDEX icuidx10_d_en_fr_ga_es ON collate_test(id) WHERE (d_en_fr_ga) = ('foo', 'bar', 'baz' COLLATE "es-x-icu");
+CREATE INDEX icuidx11_d_es ON collate_test(id) WHERE (d_es) = ('foo');
+CREATE INDEX icuidx12_custom ON collate_test(id) WHERE ('foo', 'bar')::d_custom = ('foo', 'bar' collate custom)::d_custom;
+CREATE INDEX icuidx13_custom ON collate_test(id) WHERE ('foo' collate custom, 'bar')::d_custom = ('foo', 'bar')::d_custom;
+CREATE INDEX icuidx14_myrange ON collate_test(myrange);
+CREATE INDEX icuidx15_myrange_en_fr_ga ON collate_test USING gist (myrange_en_fr_ga);
+CREATE INDEX icuidx16_mood ON collate_test(id) WHERE mood > 'ok' collate "fr-x-icu";
+CREATE TABLE collate_part(id integer, val text COLLATE "en-x-icu") PARTITION BY range(id);
+CREATE TABLE collate_part_0 PARTITION OF collate_part FOR VALUES FROM (0) TO (1);
+CREATE TABLE collate_part_1 PARTITION OF collate_part FOR VALUES FROM (1) TO (1000000);
+CREATE INDEX icuidx17_part ON collate_part_1 (val);
+-- for key columns, hash indexes should record dependency on the collation but
+-- not the version
+CREATE INDEX icuidx18_hash_d_es ON collate_test USING hash (d_es);
+CREATE INDEX icuidx19_hash_id_d_es_eq ON collate_test USING hash (id) WHERE (d_es) = 'foo';
+CREATE INDEX icuidx20_hash_id_d_es_lt ON collate_test USING hash (id) WHERE (d_es) < 'foo';
+SELECT objid::regclass, refobjid::regcollation,
+CASE
+WHEN refobjversion IS NULL THEN 'version not tracked'
+ELSE CASE
+ WHEN refobjversion = pg_collation_actual_version(refobjid) THEN 'up to date'
+ ELSE 'out of date'
+ END
+END AS version
+FROM pg_depend d
+LEFT JOIN pg_class c ON c.oid = d.objid
+WHERE refclassid = 'pg_collation'::regclass
+AND coalesce(relkind, 'i') = 'i'
+AND relname LIKE 'icuidx%'
+ORDER BY 1, 2;
+ objid | refobjid | version
+---------------------------+------------+---------------------
+ icuidx01_t_en_fr__d_es | "en-x-icu" | up to date
+ icuidx01_t_en_fr__d_es | "es-x-icu" | up to date
+ icuidx01_t_en_fr__d_es | "fr-x-icu" | up to date
+ icuidx02_d_en_fr | "en-x-icu" | up to date
+ icuidx02_d_en_fr | "fr-x-icu" | up to date
+ icuidx03_t_en_fr_ga | "en-x-icu" | up to date
+ icuidx03_t_en_fr_ga | "fr-x-icu" | up to date
+ icuidx03_t_en_fr_ga | "ga-x-icu" | up to date
+ icuidx04_d_en_fr_ga | "en-x-icu" | up to date
+ icuidx04_d_en_fr_ga | "fr-x-icu" | up to date
+ icuidx04_d_en_fr_ga | "ga-x-icu" | up to date
+ icuidx05_d_en_fr_ga_arr | "en-x-icu" | up to date
+ icuidx05_d_en_fr_ga_arr | "fr-x-icu" | up to date
+ icuidx05_d_en_fr_ga_arr | "ga-x-icu" | up to date
+ icuidx06_d_en_fr_ga | "default" | up to date
+ icuidx06_d_en_fr_ga | "en-x-icu" | up to date
+ icuidx06_d_en_fr_ga | "fr-x-icu" | up to date
+ icuidx06_d_en_fr_ga | "ga-x-icu" | up to date
+ icuidx07_d_en_fr_ga | "default" | up to date
+ icuidx07_d_en_fr_ga | "en-x-icu" | up to date
+ icuidx07_d_en_fr_ga | "fr-x-icu" | up to date
+ icuidx07_d_en_fr_ga | "ga-x-icu" | up to date
+ icuidx08_d_en_fr_ga | "en-x-icu" | up to date
+ icuidx08_d_en_fr_ga | "fr-x-icu" | up to date
+ icuidx08_d_en_fr_ga | "ga-x-icu" | up to date
+ icuidx09_d_en_fr_ga | "en-x-icu" | up to date
+ icuidx09_d_en_fr_ga | "fr-x-icu" | up to date
+ icuidx09_d_en_fr_ga | "ga-x-icu" | up to date
+ icuidx10_d_en_fr_ga_es | "en-x-icu" | up to date
+ icuidx10_d_en_fr_ga_es | "es-x-icu" | up to date
+ icuidx10_d_en_fr_ga_es | "fr-x-icu" | up to date
+ icuidx10_d_en_fr_ga_es | "ga-x-icu" | up to date
+ icuidx11_d_es | "default" | up to date
+ icuidx11_d_es | "es-x-icu" | up to date
+ icuidx12_custom | "default" | up to date
+ icuidx12_custom | custom | up to date
+ icuidx13_custom | "default" | up to date
+ icuidx13_custom | custom | up to date
+ icuidx14_myrange | "default" | up to date
+ icuidx15_myrange_en_fr_ga | "en-x-icu" | up to date
+ icuidx15_myrange_en_fr_ga | "fr-x-icu" | up to date
+ icuidx15_myrange_en_fr_ga | "ga-x-icu" | up to date
+ icuidx16_mood | "fr-x-icu" | up to date
+ icuidx17_part | "en-x-icu" | up to date
+ icuidx18_hash_d_es | "es-x-icu" | version not tracked
+ icuidx19_hash_id_d_es_eq | "default" | up to date
+ icuidx19_hash_id_d_es_eq | "es-x-icu" | up to date
+ icuidx20_hash_id_d_es_lt | "default" | up to date
+ icuidx20_hash_id_d_es_lt | "es-x-icu" | up to date
+(49 rows)
+
+UPDATE pg_depend SET refobjversion = 'not a version'
+WHERE refclassid = 'pg_collation'::regclass
+AND objid::regclass::text LIKE 'icuidx%'
+AND refobjversion IS NOT NULL;
+REINDEX TABLE collate_test;
+REINDEX TABLE collate_part_0;
+REINDEX TABLE collate_part_1;
+SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version';
+ objid
+-------
+(0 rows)
+
-- cleanup
RESET search_path;
SET client_min_messages TO warning;
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index 6ddf3a63c3..92dcda5bdc 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -2065,15 +2065,17 @@ WHERE classid = 'pg_class'::regclass AND
obj | objref | deptype
------------------------------------------+------------------------------------------------------------+---------
index concur_reindex_ind1 | constraint concur_reindex_ind1 on table concur_reindex_tab | i
+ index concur_reindex_ind2 | collation "default" | n
index concur_reindex_ind2 | column c2 of table concur_reindex_tab | a
index concur_reindex_ind3 | column c1 of table concur_reindex_tab | a
index concur_reindex_ind3 | table concur_reindex_tab | a
+ index concur_reindex_ind4 | collation "default" | n
index concur_reindex_ind4 | column c1 of table concur_reindex_tab | a
index concur_reindex_ind4 | column c1 of table concur_reindex_tab | a
index concur_reindex_ind4 | column c2 of table concur_reindex_tab | a
materialized view concur_reindex_matview | schema public | n
table concur_reindex_tab | schema public | n
-(9 rows)
+(11 rows)
REINDEX INDEX CONCURRENTLY concur_reindex_ind1;
REINDEX TABLE CONCURRENTLY concur_reindex_tab;
@@ -2093,15 +2095,17 @@ WHERE classid = 'pg_class'::regclass AND
obj | objref | deptype
------------------------------------------+------------------------------------------------------------+---------
index concur_reindex_ind1 | constraint concur_reindex_ind1 on table concur_reindex_tab | i
+ index concur_reindex_ind2 | collation "default" | n
index concur_reindex_ind2 | column c2 of table concur_reindex_tab | a
index concur_reindex_ind3 | column c1 of table concur_reindex_tab | a
index concur_reindex_ind3 | table concur_reindex_tab | a
+ index concur_reindex_ind4 | collation "default" | n
index concur_reindex_ind4 | column c1 of table concur_reindex_tab | a
index concur_reindex_ind4 | column c1 of table concur_reindex_tab | a
index concur_reindex_ind4 | column c2 of table concur_reindex_tab | a
materialized view concur_reindex_matview | schema public | n
table concur_reindex_tab | schema public | n
-(9 rows)
+(11 rows)
-- Check that comments are preserved
CREATE TABLE testcomment (i int);
diff --git a/src/test/regress/sql/collate.icu.utf8.sql b/src/test/regress/sql/collate.icu.utf8.sql
index 35acf91fbf..c8f1a620d2 100644
--- a/src/test/regress/sql/collate.icu.utf8.sql
+++ b/src/test/regress/sql/collate.icu.utf8.sql
@@ -716,6 +716,89 @@ INSERT INTO test33 VALUES (2, 'DEF');
-- they end up in the same partition (but it's platform-dependent which one)
SELECT (SELECT count(*) FROM test33_0) <> (SELECT count(*) FROM test33_1);
+-- collation versioning support
+CREATE TYPE t_en_fr AS (fr text COLLATE "fr-x-icu", en text COLLATE "en-x-icu");
+CREATE DOMAIN d_en_fr AS t_en_fr;
+CREATE DOMAIN d_es AS text COLLATE "es-x-icu";
+CREATE TYPE t_en_fr_ga AS (en_fr t_en_fr, ga text COLLATE "ga-x-icu");
+CREATE DOMAIN d_en_fr_ga AS t_en_fr_ga;
+CREATE TYPE t_custom AS (meh text, meh2 text);
+CREATE DOMAIN d_custom AS t_custom;
+
+CREATE COLLATION custom (
+ LOCALE = 'fr-x-icu', PROVIDER = 'icu'
+);
+
+CREATE TYPE myrange AS range (subtype = text);
+CREATE TYPE myrange_en_fr_ga AS range(subtype = t_en_fr_ga);
+
+CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
+
+CREATE TABLE collate_test (
+ id integer,
+ t_en_fr t_en_fr,
+ d_en_fr d_en_fr,
+ d_es d_es,
+ t_en_fr_ga t_en_fr_ga,
+ d_en_fr_ga d_en_fr_ga,
+ d_en_fr_ga_arr d_en_fr_ga[],
+ myrange myrange,
+ myrange_en_fr_ga myrange_en_fr_ga,
+ mood mood
+);
+
+CREATE INDEX icuidx01_t_en_fr__d_es ON collate_test (t_en_fr, d_es);
+CREATE INDEX icuidx02_d_en_fr ON collate_test (d_en_fr);
+CREATE INDEX icuidx03_t_en_fr_ga ON collate_test (t_en_fr_ga);
+CREATE INDEX icuidx04_d_en_fr_ga ON collate_test (d_en_fr_ga);
+CREATE INDEX icuidx05_d_en_fr_ga_arr ON collate_test (d_en_fr_ga_arr);
+CREATE INDEX icuidx06_d_en_fr_ga ON collate_test(id) WHERE (d_en_fr_ga).en_fr.fr = 'foo';
+CREATE INDEX icuidx07_d_en_fr_ga ON collate_test(id) WHERE (d_en_fr_ga).ga = 'foo';
+CREATE INDEX icuidx08_d_en_fr_ga ON collate_test(id) WHERE (t_en_fr_ga) = ('foo', 'bar', 'baz');
+CREATE INDEX icuidx09_d_en_fr_ga ON collate_test(id) WHERE (d_en_fr_ga) = ('foo', 'bar', 'baz');
+CREATE INDEX icuidx10_d_en_fr_ga_es ON collate_test(id) WHERE (d_en_fr_ga) = ('foo', 'bar', 'baz' COLLATE "es-x-icu");
+CREATE INDEX icuidx11_d_es ON collate_test(id) WHERE (d_es) = ('foo');
+CREATE INDEX icuidx12_custom ON collate_test(id) WHERE ('foo', 'bar')::d_custom = ('foo', 'bar' collate custom)::d_custom;
+CREATE INDEX icuidx13_custom ON collate_test(id) WHERE ('foo' collate custom, 'bar')::d_custom = ('foo', 'bar')::d_custom;
+CREATE INDEX icuidx14_myrange ON collate_test(myrange);
+CREATE INDEX icuidx15_myrange_en_fr_ga ON collate_test USING gist (myrange_en_fr_ga);
+CREATE INDEX icuidx16_mood ON collate_test(id) WHERE mood > 'ok' collate "fr-x-icu";
+
+CREATE TABLE collate_part(id integer, val text COLLATE "en-x-icu") PARTITION BY range(id);
+CREATE TABLE collate_part_0 PARTITION OF collate_part FOR VALUES FROM (0) TO (1);
+CREATE TABLE collate_part_1 PARTITION OF collate_part FOR VALUES FROM (1) TO (1000000);
+CREATE INDEX icuidx17_part ON collate_part_1 (val);
+-- for key columns, hash indexes should record dependency on the collation but
+-- not the version
+CREATE INDEX icuidx18_hash_d_es ON collate_test USING hash (d_es);
+CREATE INDEX icuidx19_hash_id_d_es_eq ON collate_test USING hash (id) WHERE (d_es) = 'foo';
+CREATE INDEX icuidx20_hash_id_d_es_lt ON collate_test USING hash (id) WHERE (d_es) < 'foo';
+
+SELECT objid::regclass, refobjid::regcollation,
+CASE
+WHEN refobjversion IS NULL THEN 'version not tracked'
+ELSE CASE
+ WHEN refobjversion = pg_collation_actual_version(refobjid) THEN 'up to date'
+ ELSE 'out of date'
+ END
+END AS version
+FROM pg_depend d
+LEFT JOIN pg_class c ON c.oid = d.objid
+WHERE refclassid = 'pg_collation'::regclass
+AND coalesce(relkind, 'i') = 'i'
+AND relname LIKE 'icuidx%'
+ORDER BY 1, 2;
+
+UPDATE pg_depend SET refobjversion = 'not a version'
+WHERE refclassid = 'pg_collation'::regclass
+AND objid::regclass::text LIKE 'icuidx%'
+AND refobjversion IS NOT NULL;
+
+REINDEX TABLE collate_test;
+REINDEX TABLE collate_part_0;
+REINDEX TABLE collate_part_1;
+
+SELECT objid::regclass FROM pg_depend WHERE refobjversion = 'not a version';
-- cleanup
RESET search_path;
--
2.20.1