diff options
author | Thomas Munro | 2020-11-02 06:50:45 +0000 |
---|---|---|
committer | Thomas Munro | 2020-11-02 12:19:50 +0000 |
commit | 257836a75585934cc05ed7a80bccf8190d41e056 (patch) | |
tree | 5f3eb018d0f0609063669b81136036b79bf8f948 /src/backend | |
parent | cd6f479e79f3a33ef7a919c6b6c0c498c790f154 (diff) |
Track collation versions for indexes.
Record the current version of dependent collations in pg_depend when
creating or rebuilding an index. When accessing the index later, warn
that the index may be corrupted if the current version doesn't match.
Thanks to Douglas Doole, Peter Eisentraut, Christoph Berg, Laurenz Albe,
Michael Paquier, Robert Haas, Tom Lane and others for very helpful
discussion.
Author: Thomas Munro <thomas.munro@gmail.com>
Author: Julien Rouhaud <rjuju123@gmail.com>
Reviewed-by: Peter Eisentraut <peter.eisentraut@2ndquadrant.com> (earlier versions)
Discussion: https://postgr.es/m/CAEepm%3D0uEQCpfq_%2BLYFBdArCe4Ot98t1aR4eYiYTe%3DyavQygiQ%40mail.gmail.com
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/catalog/dependency.c | 182 | ||||
-rw-r--r-- | src/backend/catalog/heap.c | 7 | ||||
-rw-r--r-- | src/backend/catalog/index.c | 197 | ||||
-rw-r--r-- | src/backend/catalog/pg_constraint.c | 2 | ||||
-rw-r--r-- | src/backend/catalog/pg_depend.c | 46 | ||||
-rw-r--r-- | src/backend/catalog/pg_type.c | 60 | ||||
-rw-r--r-- | src/backend/commands/collationcmds.c | 16 | ||||
-rw-r--r-- | src/backend/commands/tablecmds.c | 31 | ||||
-rw-r--r-- | src/backend/nodes/copyfuncs.c | 1 | ||||
-rw-r--r-- | src/backend/optimizer/util/plancat.c | 9 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 8 | ||||
-rw-r--r-- | src/backend/utils/adt/pg_locale.c | 46 | ||||
-rw-r--r-- | src/backend/utils/adt/pg_upgrade_support.c | 1 | ||||
-rw-r--r-- | src/backend/utils/cache/relcache.c | 2 |
14 files changed, 541 insertions, 67 deletions
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 1a927377e73..b0d037600e9 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" @@ -435,6 +436,84 @@ performMultipleDeletions(const ObjectAddresses *objects, } /* + * Call a function for all objects that 'object' depend on. If the function + * returns true, refobjversion will be updated in the catalog. + */ +void +visitDependenciesOf(const ObjectAddress *object, + VisitDependenciesOfCB 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 *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); + + /* Does the callback want to update the version? */ + if (callback(&otherObject, + isnull ? NULL : TextDatumGetCString(depversion), + &new_version, + userdata)) + { + 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)); + + if (new_version) + values[Anum_pg_depend_refobjversion - 1] = + CStringGetTextDatum(new_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); + } + } + systable_endscan(scan); + table_close(depRel, RowExclusiveLock); +} + +/* * findDependentObjects - find all objects that depend on 'object' * * For every object that depends on the starting object, acquire a deletion @@ -1567,6 +1646,38 @@ ReleaseDeletionLock(const ObjectAddress *object) } /* + * Record dependencies on a list of collations, optionally with their current + * version. + */ +void +recordDependencyOnCollations(ObjectAddress *myself, + List *collations, + bool record_version) +{ + ObjectAddresses *addrs; + ListCell *lc; + + if (list_length(collations) == 0) + return; + + addrs = new_object_addresses(); + foreach(lc, collations) + { + ObjectAddress referenced; + + ObjectAddressSet(referenced, CollationRelationId, lfirst_oid(lc)); + + add_exact_object_address(&referenced, addrs); + } + + eliminate_duplicate_dependencies(addrs); + recordMultipleDependencies(myself, addrs->refs, addrs->numrefs, + DEPENDENCY_NORMAL, record_version); + + free_object_addresses(addrs); +} + +/* * recordDependencyOnExpr - find expression dependencies * * This is used to find the dependencies of rules, constraint expressions, @@ -1602,8 +1713,8 @@ recordDependencyOnExpr(const ObjectAddress *depender, recordMultipleDependencies(depender, context.addrs->refs, context.addrs->numrefs, - NULL, - behavior); + behavior, + false); free_object_addresses(context.addrs); } @@ -1630,7 +1741,8 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender, Node *expr, Oid relId, DependencyType behavior, DependencyType self_behavior, - bool reverse_self) + bool reverse_self, + bool record_version) { find_expr_references_context context; RangeTblEntry rte; @@ -1691,8 +1803,8 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender, recordMultipleDependencies(depender, self_addrs->refs, self_addrs->numrefs, - NULL, - self_behavior); + self_behavior, + record_version); else { /* Can't use recordMultipleDependencies, so do it the hard way */ @@ -1713,8 +1825,8 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender, recordMultipleDependencies(depender, context.addrs->refs, context.addrs->numrefs, - NULL, - behavior); + behavior, + record_version); free_object_addresses(context.addrs); } @@ -1770,6 +1882,29 @@ 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); + + /* Top-level collation if valid */ + if (OidIsValid(var->varcollid)) + add_object_address(OCLASS_COLLATION, var->varcollid, 0, + context->addrs); + /* Otherwise, it may be a type with internal collations */ + else if (var->vartype >= FirstNormalObjectId) + { + List *collations; + ListCell *lc; + + collations = GetTypeCollations(var->vartype); + + foreach(lc, collations) + { + Oid coll = lfirst_oid(lc); + + if (OidIsValid(coll)) + add_object_address(OCLASS_COLLATION, + lfirst_oid(lc), 0, + context->addrs); + } + } } /* @@ -1794,11 +1929,9 @@ 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. */ - if (OidIsValid(con->constcollid) && - con->constcollid != DEFAULT_COLLATION_OID) + if (OidIsValid(con->constcollid)) add_object_address(OCLASS_COLLATION, con->constcollid, 0, context->addrs); @@ -1887,8 +2020,7 @@ find_expr_references_walker(Node *node, add_object_address(OCLASS_TYPE, param->paramtype, 0, context->addrs); /* and its collation, just as for Consts */ - if (OidIsValid(param->paramcollid) && - param->paramcollid != DEFAULT_COLLATION_OID) + if (OidIsValid(param->paramcollid)) add_object_address(OCLASS_COLLATION, param->paramcollid, 0, context->addrs); } @@ -1975,8 +2107,7 @@ find_expr_references_walker(Node *node, add_object_address(OCLASS_TYPE, fselect->resulttype, 0, context->addrs); /* the collation might not be referenced anywhere else, either */ - if (OidIsValid(fselect->resultcollid) && - fselect->resultcollid != DEFAULT_COLLATION_OID) + if (OidIsValid(fselect->resultcollid)) add_object_address(OCLASS_COLLATION, fselect->resultcollid, 0, context->addrs); } @@ -2006,8 +2137,7 @@ find_expr_references_walker(Node *node, add_object_address(OCLASS_TYPE, relab->resulttype, 0, context->addrs); /* the collation might not be referenced anywhere else, either */ - if (OidIsValid(relab->resultcollid) && - relab->resultcollid != DEFAULT_COLLATION_OID) + if (OidIsValid(relab->resultcollid)) add_object_address(OCLASS_COLLATION, relab->resultcollid, 0, context->addrs); } @@ -2019,8 +2149,7 @@ find_expr_references_walker(Node *node, add_object_address(OCLASS_TYPE, iocoerce->resulttype, 0, context->addrs); /* the collation might not be referenced anywhere else, either */ - if (OidIsValid(iocoerce->resultcollid) && - iocoerce->resultcollid != DEFAULT_COLLATION_OID) + if (OidIsValid(iocoerce->resultcollid)) add_object_address(OCLASS_COLLATION, iocoerce->resultcollid, 0, context->addrs); } @@ -2032,8 +2161,7 @@ find_expr_references_walker(Node *node, add_object_address(OCLASS_TYPE, acoerce->resulttype, 0, context->addrs); /* the collation might not be referenced anywhere else, either */ - if (OidIsValid(acoerce->resultcollid) && - acoerce->resultcollid != DEFAULT_COLLATION_OID) + if (OidIsValid(acoerce->resultcollid)) add_object_address(OCLASS_COLLATION, acoerce->resultcollid, 0, context->addrs); /* fall through to examine arguments */ @@ -2121,8 +2249,7 @@ find_expr_references_walker(Node *node, if (OidIsValid(wc->endInRangeFunc)) add_object_address(OCLASS_PROC, wc->endInRangeFunc, 0, context->addrs); - if (OidIsValid(wc->inRangeColl) && - wc->inRangeColl != DEFAULT_COLLATION_OID) + if (OidIsValid(wc->inRangeColl)) add_object_address(OCLASS_COLLATION, wc->inRangeColl, 0, context->addrs); /* fall through to examine substructure */ @@ -2267,7 +2394,7 @@ find_expr_references_walker(Node *node, { Oid collid = lfirst_oid(ct); - if (OidIsValid(collid) && collid != DEFAULT_COLLATION_OID) + if (OidIsValid(collid)) add_object_address(OCLASS_COLLATION, collid, 0, context->addrs); } @@ -2289,7 +2416,7 @@ find_expr_references_walker(Node *node, { Oid collid = lfirst_oid(ct); - if (OidIsValid(collid) && collid != DEFAULT_COLLATION_OID) + if (OidIsValid(collid)) add_object_address(OCLASS_COLLATION, collid, 0, context->addrs); } @@ -2685,8 +2812,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 9ccf378d45b..4cd7d769381 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -2336,7 +2336,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, */ recordDependencyOnSingleRelExpr(&colobject, expr, RelationGetRelid(rel), DEPENDENCY_AUTO, - DEPENDENCY_AUTO, false); + DEPENDENCY_AUTO, false, false); } else { @@ -2346,7 +2346,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, */ recordDependencyOnSingleRelExpr(&defobject, expr, RelationGetRelid(rel), DEPENDENCY_NORMAL, - DEPENDENCY_NORMAL, false); + DEPENDENCY_NORMAL, false, false); } /* @@ -3706,7 +3706,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 a18cc7cad30..b88b4a1f12b 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/rel.h" @@ -1020,6 +1022,8 @@ index_create(Relation heapRelation, ObjectAddress myself, referenced; ObjectAddresses *addrs; + List *colls = NIL, + *colls_no_version = NIL; ObjectAddressSet(myself, RelationRelationId, indexRelationId); @@ -1103,30 +1107,65 @@ index_create(Relation heapRelation, recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_SEC); } - /* placeholder for normal dependencies */ - addrs = new_object_addresses(); - - /* Store dependency on collations */ - - /* The default collation is pinned, so don't bother recording it */ + /* Get 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)) { - ObjectAddressSet(referenced, CollationRelationId, - collationObjectId[i]); - add_exact_object_address(&referenced, addrs); + Oid opclass = classObjectId[i]; + + /* + * The *_pattern_ops opclasses are special: they explicitly do + * not depend on collation order so we can save some effort. + * + * XXX With more analysis, we could also skip version tracking + * for some cases like hash indexes with deterministic + * collations, because they will never need to order strings. + */ + if (opclass == TEXT_BTREE_PATTERN_OPS_OID || + opclass == VARCHAR_BTREE_PATTERN_OPS_OID || + opclass == BPCHAR_BTREE_PATTERN_OPS_OID) + colls_no_version = lappend_oid(colls_no_version, colloid); + else + colls = lappend_oid(colls, colloid); + } + else + { + Form_pg_attribute att = TupleDescAttr(indexTupDesc, i); + + Assert(i < indexTupDesc->natts); + + /* + * Even though there is no top-level collation, there may be + * collations affecting ordering inside types, so look there + * too. + */ + colls = list_concat(colls, GetTypeCollations(att->atttypid)); } } + /* + * If we have anything in both lists, keep just the versioned one to + * avoid some duplication. + */ + if (colls_no_version != NIL && colls != NIL) + colls_no_version = list_difference_oid(colls_no_version, colls); + + /* Store the versioned and unversioned collation dependencies. */ + if (colls_no_version != NIL) + recordDependencyOnCollations(&myself, colls_no_version, false); + if (colls != NIL) + recordDependencyOnCollations(&myself, colls, true); + /* Store dependency on operator classes */ + addrs = new_object_addresses(); for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++) { ObjectAddressSet(referenced, OperatorClassRelationId, classObjectId[i]); add_exact_object_address(&referenced, addrs); } - record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); free_object_addresses(addrs); @@ -1137,7 +1176,7 @@ index_create(Relation heapRelation, (Node *) indexInfo->ii_Expressions, heapRelationId, DEPENDENCY_NORMAL, - DEPENDENCY_AUTO, false); + DEPENDENCY_AUTO, false, true); } /* Store dependencies on anything mentioned in predicate */ @@ -1147,7 +1186,7 @@ index_create(Relation heapRelation, (Node *) indexInfo->ii_Predicate, heapRelationId, DEPENDENCY_NORMAL, - DEPENDENCY_AUTO, false); + DEPENDENCY_AUTO, false, true); } } else @@ -1226,6 +1265,129 @@ index_create(Relation heapRelation, return indexRelationId; } +typedef struct do_collation_version_check_context +{ + Oid relid; + List *warned_colls; +} do_collation_version_check_context; + +/* + * Raise a warning if the recorded and current collation version don't match. + * This is a callback for visitDependenciesOf(). + */ +static bool +do_collation_version_check(const ObjectAddress *otherObject, + const char *version, + char **new_version, + void *data) +{ + do_collation_version_check_context *context = data; + char *current_version; + + /* We only care about dependencies on collations with a version. */ + if (!version || otherObject->classId != CollationRelationId) + return false; + + /* Ask the provider for the current version. Give up if unsupported. */ + current_version = get_collation_version_for_oid(otherObject->objectId); + if (!current_version) + return false; + + /* + * We don't expect too many duplicates, but it's possible, and we don't + * want to generate duplicate warnings. + */ + if (list_member_oid(context->warned_colls, otherObject->objectId)) + return false; + + /* Do they match? */ + if (strcmp(current_version, version) != 0) + { + /* + * The version has changed, probably due to an OS/library upgrade or + * streaming replication between different OS/library versions. + */ + ereport(WARNING, + (errmsg("index \"%s\" depends on collation \"%s\" version \"%s\", but the current version is \"%s\"", + get_rel_name(context->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."))); + + /* Remember not to complain about this collation again. */ + context->warned_colls = lappend_oid(context->warned_colls, + otherObject->objectId); + } + + return false; +} + +/* index_check_collation_versions + * Check the collation version for all dependencies on the given index. + */ +void +index_check_collation_versions(Oid relid) +{ + do_collation_version_check_context context; + ObjectAddress object; + + /* + * The callback needs the relid for error messages, and some scratch space + * to avoid duplicate warnings. + */ + context.relid = relid; + context.warned_colls = NIL; + + object.classId = RelationRelationId; + object.objectId = relid; + object.objectSubId = 0; + + visitDependenciesOf(&object, &do_collation_version_check, &context); + + list_free(context.warned_colls); +} + +/* + * Update the version for collations. A callback for visitDependenciesOf(). + */ +static bool +do_collation_version_update(const ObjectAddress *otherObject, + const char *version, + char **new_version, + void *data) +{ + Oid *coll = data; + + /* We only care about dependencies on collations with versions. */ + if (!version || otherObject->classId != CollationRelationId) + return false; + + /* If we're only trying to update one collation, skip others. */ + if (OidIsValid(*coll) && otherObject->objectId != *coll) + return false; + + *new_version = get_collation_version_for_oid(otherObject->objectId); + + return true; +} + +/* + * Record the current versions of one or all collations that an index depends + * on. InvalidOid means all. + */ +void +index_update_collation_versions(Oid relid, Oid coll) +{ + ObjectAddress object; + + object.classId = RelationRelationId; + object.objectId = relid; + object.objectSubId = 0; + visitDependenciesOf(&object, &do_collation_version_update, &coll); +} + /* * index_concurrently_create_copy * @@ -1686,6 +1848,10 @@ index_concurrently_swap(Oid newIndexId, Oid oldIndexId, const char *oldName) changeDependenciesOf(RelationRelationId, oldIndexId, newIndexId); changeDependenciesOn(RelationRelationId, oldIndexId, newIndexId); + /* Now we have the old index's collation versions, so fix that. */ + CommandCounterIncrement(); + index_update_collation_versions(newIndexId, InvalidOid); + /* * Copy over statistics from old to new index */ @@ -3638,6 +3804,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, InvalidOid); } /* diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c index 0d70cb0c3c9..93774c9d21a 100644 --- a/src/backend/catalog/pg_constraint.c +++ b/src/backend/catalog/pg_constraint.c @@ -362,7 +362,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 09c30b13e88..25290c821fd 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,6 +28,7 @@ #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" +#include "utils/pg_locale.h" #include "utils/rel.h" @@ -45,19 +47,24 @@ recordDependencyOn(const ObjectAddress *depender, const ObjectAddress *referenced, DependencyType behavior) { - recordMultipleDependencies(depender, referenced, 1, NULL, behavior); + recordMultipleDependencies(depender, referenced, 1, behavior, false); } /* * Record multiple dependencies (of the same kind) for a single dependent * object. This has a little less overhead than recording each separately. + * + * If record_version is true, then a record is added even if the referenced + * object 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 record_version) { Relation dependDesc; CatalogIndexState indstate; @@ -66,6 +73,7 @@ recordMultipleDependencies(const ObjectAddress *depender, max_slots, slot_init_count, slot_stored_count; + char *version = NULL; if (nreferenced <= 0) return; /* nothing to do */ @@ -96,12 +104,38 @@ recordMultipleDependencies(const ObjectAddress *depender, slot_init_count = 0; for (i = 0; i < nreferenced; i++, referenced++) { + bool ignore_systempin = false; + + if (record_version) + { + /* For now we only know how to deal with collations. */ + if (referenced->classId == CollationRelationId) + { + /* C and POSIX don't need version tracking. */ + if (referenced->objectId == C_COLLATION_OID || + referenced->objectId == POSIX_COLLATION_OID) + continue; + + version = get_collation_version_for_oid(referenced->objectId); + + /* + * 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; + } + } + 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)) continue; if (slot_init_count < max_slots) diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c index 0b04dff7731..44eed1a0b34 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" @@ -513,6 +514,65 @@ TypeCreate(Oid newTypeOid, } /* + * Get a list of all distinct collations that the given type depends on. + */ +List * +GetTypeCollations(Oid typeoid) +{ + 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)) + 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)) + result = list_append_unique_oid(result, att->attcollation); + else + result = list_concat_unique_oid(result, + GetTypeCollations(att->atttypid)); + } + + relation_close(rel, NoLock); + } + else if (typeTup->typtype == TYPTYPE_DOMAIN) + { + Assert(OidIsValid(typeTup->typbasetype)); + + result = list_concat_unique_oid(result, + GetTypeCollations(typeTup->typbasetype)); + } + else if (typeTup->typtype == TYPTYPE_RANGE) + { + Oid rangeid = get_range_subtype(typeTup->oid); + + Assert(OidIsValid(rangeid)); + + result = list_concat_unique_oid(result, GetTypeCollations(rangeid)); + } + else if (OidIsValid(typeTup->typelem)) + result = list_concat_unique_oid(result, + GetTypeCollations(typeTup->typelem)); + + ReleaseSysCache(tuple); + + return result; +} + +/* * GenerateTypeDependencies: build the dependencies needed for a type * * Most of what this function needs to know about the type is passed as the diff --git a/src/backend/commands/collationcmds.c b/src/backend/commands/collationcmds.c index 5ad8886e60a..7b31272734d 100644 --- a/src/backend/commands/collationcmds.c +++ b/src/backend/commands/collationcmds.c @@ -270,23 +270,9 @@ 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); if (version) PG_RETURN_TEXT_P(cstring_to_text(version)); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index df13b72974f..1b105ba1c42 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -93,6 +93,7 @@ #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/partcache.h" +#include "utils/pg_locale.h" #include "utils/relcache.h" #include "utils/ruleutils.h" #include "utils/snapmgr.h" @@ -559,6 +560,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx, Relation partitionTbl); static List *GetParentedForeignKeyRefs(Relation partition); static void ATDetachCheckNoForeignKeyRefs(Relation partition); +static void ATExecAlterCollationRefreshVersion(Relation rel, List *coll); /* ---------------------------------------------------------------- @@ -3986,6 +3988,10 @@ AlterTableGetLockLevel(List *cmds) cmd_lockmode = AccessShareLock; break; + case AT_AlterCollationRefreshVersion: + cmd_lockmode = AccessExclusiveLock; + break; + default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype); @@ -4160,6 +4166,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* This command never recurses */ pass = AT_PASS_MISC; break; + case AT_AlterCollationRefreshVersion: /* ALTER COLLATION ... REFRESH + * VERSION */ + ATSimplePermissions(rel, ATT_INDEX); + /* This command never recurses */ + pass = AT_PASS_MISC; + break; case AT_SetStorage: /* ALTER COLUMN SET STORAGE */ ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_FOREIGN_TABLE); ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode, context); @@ -4738,6 +4750,11 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name); break; + case AT_AlterCollationRefreshVersion: + /* ATPrepCmd ensured it must be an index */ + Assert(rel->rd_rel->relkind == RELKIND_INDEX); + ATExecAlterCollationRefreshVersion(rel, cmd->object); + break; default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype); @@ -17582,3 +17599,17 @@ ATDetachCheckNoForeignKeyRefs(Relation partition) table_close(rel, NoLock); } } + +/* + * ALTER INDEX ... ALTER COLLATION ... REFRESH VERSION + * + * Update refobjversion to the current collation version by force. This clears + * warnings about version mismatches without the need to run REINDEX, + * potentially hiding corruption due to ordering changes. + */ +static void +ATExecAlterCollationRefreshVersion(Relation rel, List *coll) +{ + index_update_collation_versions(rel->rd_id, get_collation_oid(coll, false)); + CacheInvalidateRelcache(rel); +} diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index ac8b57109c5..530aac68a76 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3215,6 +3215,7 @@ _copyAlterTableCmd(const AlterTableCmd *from) COPY_SCALAR_FIELD(subtype); COPY_STRING_FIELD(name); + COPY_NODE_FIELD(object); COPY_SCALAR_FIELD(num); COPY_NODE_FIELD(newowner); COPY_NODE_FIELD(def); diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 4f0da51c269..52c01eb86b1 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/parser/gram.y b/src/backend/parser/gram.y index 60cf7242a30..357ab93fb65 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -2591,6 +2591,14 @@ alter_table_cmd: n->subtype = AT_NoForceRowSecurity; $$ = (Node *)n; } + /* ALTER INDEX <name> ALTER COLLATION ... REFRESH VERSION */ + | ALTER COLLATION any_name REFRESH VERSION_P + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_AlterCollationRefreshVersion; + n->object = $3; + $$ = (Node *)n; + } | alter_generic_options { AlterTableCmd *n = makeNode(AlterTableCmd); diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c index 514e0fa0af8..3b0324ce18a 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" @@ -139,6 +141,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 * @@ -1630,7 +1635,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; @@ -1712,6 +1717,45 @@ get_collation_actual_version(char collprovider, const char *collcollate) return collversion; } +/* + * Get provider-specific collation version string for a given collation OID. + * Return NULL if the provider doesn't support versions. + */ +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); + + 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 14d9eb2b5b3..8a5953881ee 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" diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 9224e2ffeda..66393becfb6 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" @@ -5934,6 +5935,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; |