Commit 6eb71ac5 authored by Robert Haas's avatar Robert Haas

Make CheckIndexCompatible simpler and more bullet-proof.

This gives up the "don't rewrite the index" behavior in a couple of
relatively unimportant cases, such as changing between an array type
and an unconstrained domain over that array type, in return for
making this code more future-proof.

Noah Misch
parent 8366c780
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include "catalog/pg_opclass.h" #include "catalog/pg_opclass.h"
#include "catalog/pg_opfamily.h" #include "catalog/pg_opfamily.h"
#include "catalog/pg_tablespace.h" #include "catalog/pg_tablespace.h"
#include "catalog/pg_type.h"
#include "commands/dbcommands.h" #include "commands/dbcommands.h"
#include "commands/defrem.h" #include "commands/defrem.h"
#include "commands/tablecmds.h" #include "commands/tablecmds.h"
...@@ -52,6 +53,7 @@ ...@@ -52,6 +53,7 @@
/* non-export function prototypes */ /* non-export function prototypes */
static void CheckPredicate(Expr *predicate); static void CheckPredicate(Expr *predicate);
static void ComputeIndexAttrs(IndexInfo *indexInfo, static void ComputeIndexAttrs(IndexInfo *indexInfo,
Oid *typeOidP,
Oid *collationOidP, Oid *collationOidP,
Oid *classOidP, Oid *classOidP,
int16 *colOptionP, int16 *colOptionP,
...@@ -87,18 +89,17 @@ static void RangeVarCallbackForReindexIndex(const RangeVar *relation, ...@@ -87,18 +89,17 @@ static void RangeVarCallbackForReindexIndex(const RangeVar *relation,
* of columns and that if one has an expression column or predicate, both do. * of columns and that if one has an expression column or predicate, both do.
* Errors arising from the attribute list still apply. * Errors arising from the attribute list still apply.
* *
* Most column type changes that can skip a table rewrite will not invalidate * Most column type changes that can skip a table rewrite do not invalidate
* indexes. For btree and hash indexes, we assume continued validity when * indexes. We ackowledge this when all operator classes, collations and
* each column of an index would have the same operator family before and * exclusion operators match. Though we could further permit intra-opfamily
* after the change. Since we do not document a contract for GIN or GiST * changes for btree and hash indexes, that adds subtle complexity with no
* operator families, we require an exact operator class match for them and * concrete benefit for core types.
* for any other access methods.
* * When a comparison or exclusion operator has a polymorphic input type, the
* DefineIndex always verifies that each exclusion operator shares an operator * actual input types must also match. This defends against the possibility
* family with its corresponding index operator class. For access methods * that operators could vary behavior in response to get_fn_expr_argtype().
* having no operator family contract, confirm that the old and new indexes * At present, this hazard is theoretical: check_exclusion_constraint() and
* use the exact same exclusion operator. For btree and hash, there's nothing * all core index access methods decline to set fn_expr for such calls.
* more to check.
* *
* We do not yet implement a test to verify compatibility of expression * We do not yet implement a test to verify compatibility of expression
* columns or predicates, so assume any such index is incompatible. * columns or predicates, so assume any such index is incompatible.
...@@ -111,6 +112,7 @@ CheckIndexCompatible(Oid oldId, ...@@ -111,6 +112,7 @@ CheckIndexCompatible(Oid oldId,
List *exclusionOpNames) List *exclusionOpNames)
{ {
bool isconstraint; bool isconstraint;
Oid *typeObjectId;
Oid *collationObjectId; Oid *collationObjectId;
Oid *classObjectId; Oid *classObjectId;
Oid accessMethodId; Oid accessMethodId;
...@@ -123,10 +125,10 @@ CheckIndexCompatible(Oid oldId, ...@@ -123,10 +125,10 @@ CheckIndexCompatible(Oid oldId,
int numberOfAttributes; int numberOfAttributes;
int old_natts; int old_natts;
bool isnull; bool isnull;
bool family_am;
bool ret = true; bool ret = true;
oidvector *old_indclass; oidvector *old_indclass;
oidvector *old_indcollation; oidvector *old_indcollation;
Relation irel;
int i; int i;
Datum d; Datum d;
...@@ -168,10 +170,12 @@ CheckIndexCompatible(Oid oldId, ...@@ -168,10 +170,12 @@ CheckIndexCompatible(Oid oldId,
indexInfo->ii_ExclusionOps = NULL; indexInfo->ii_ExclusionOps = NULL;
indexInfo->ii_ExclusionProcs = NULL; indexInfo->ii_ExclusionProcs = NULL;
indexInfo->ii_ExclusionStrats = NULL; indexInfo->ii_ExclusionStrats = NULL;
typeObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
collationObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid)); collationObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid)); classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16)); coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
ComputeIndexAttrs(indexInfo, collationObjectId, classObjectId, ComputeIndexAttrs(indexInfo,
typeObjectId, collationObjectId, classObjectId,
coloptions, attributeList, coloptions, attributeList,
exclusionOpNames, relationId, exclusionOpNames, relationId,
accessMethodName, accessMethodId, accessMethodName, accessMethodId,
...@@ -191,12 +195,7 @@ CheckIndexCompatible(Oid oldId, ...@@ -191,12 +195,7 @@ CheckIndexCompatible(Oid oldId,
return false; return false;
} }
/* /* Any change in operator class or collation breaks compatibility. */
* If the old and new operator class of any index column differ in
* operator family or collation, regard the old index as incompatible.
* For access methods other than btree and hash, a family match has no
* defined meaning; require an exact operator class match.
*/
old_natts = ((Form_pg_index) GETSTRUCT(tuple))->indnatts; old_natts = ((Form_pg_index) GETSTRUCT(tuple))->indnatts;
Assert(old_natts == numberOfAttributes); Assert(old_natts == numberOfAttributes);
...@@ -208,52 +207,42 @@ CheckIndexCompatible(Oid oldId, ...@@ -208,52 +207,42 @@ CheckIndexCompatible(Oid oldId,
Assert(!isnull); Assert(!isnull);
old_indclass = (oidvector *) DatumGetPointer(d); old_indclass = (oidvector *) DatumGetPointer(d);
family_am = accessMethodId == BTREE_AM_OID || accessMethodId == HASH_AM_OID; ret = (memcmp(old_indclass->values, classObjectId,
old_natts * sizeof(Oid)) == 0 &&
for (i = 0; i < old_natts; i++) memcmp(old_indcollation->values, collationObjectId,
{ old_natts * sizeof(Oid)) == 0);
Oid old_class = old_indclass->values[i];
Oid new_class = classObjectId[i];
if (!(old_indcollation->values[i] == collationObjectId[i]
&& (old_class == new_class
|| (family_am && (get_opclass_family(old_class)
== get_opclass_family(new_class))))))
{
ret = false;
break;
}
}
ReleaseSysCache(tuple); ReleaseSysCache(tuple);
/* /* For polymorphic opcintype, column type changes break compatibility. */
* For btree and hash, exclusion operators need only fall in the same irel = index_open(oldId, AccessShareLock); /* caller probably has a lock */
* operator family; ComputeIndexAttrs already verified that much. If we for (i = 0; i < old_natts && ret; i++)
* get this far, we know that the index operator family has not changed, ret = (!IsPolymorphicType(get_opclass_input_type(classObjectId[i])) ||
* and we're done. For other access methods, require exact matches for irel->rd_att->attrs[i]->atttypid == typeObjectId[i]);
* all exclusion operators.
*/ /* Any change in exclusion operator selections breaks compatibility. */
if (ret && !family_am && indexInfo->ii_ExclusionOps != NULL) if (ret && indexInfo->ii_ExclusionOps != NULL)
{ {
Relation irel;
Oid *old_operators, *old_procs; Oid *old_operators, *old_procs;
uint16 *old_strats; uint16 *old_strats;
/* Caller probably already holds a stronger lock. */
irel = index_open(oldId, AccessShareLock);
RelationGetExclusionInfo(irel, &old_operators, &old_procs, &old_strats); RelationGetExclusionInfo(irel, &old_operators, &old_procs, &old_strats);
ret = memcmp(old_operators, indexInfo->ii_ExclusionOps,
old_natts * sizeof(Oid)) == 0;
for (i = 0; i < old_natts; i++) /* Require an exact input type match for polymorphic operators. */
if (old_operators[i] != indexInfo->ii_ExclusionOps[i]) for (i = 0; i < old_natts && ret; i++)
{ {
ret = false; Oid left,
break; right;
}
index_close(irel, NoLock); op_input_types(indexInfo->ii_ExclusionOps[i], &left, &right);
ret = (!(IsPolymorphicType(left) || IsPolymorphicType(right)) ||
irel->rd_att->attrs[i]->atttypid == typeObjectId[i]);
}
} }
index_close(irel, NoLock);
return ret; return ret;
} }
...@@ -315,6 +304,7 @@ DefineIndex(RangeVar *heapRelation, ...@@ -315,6 +304,7 @@ DefineIndex(RangeVar *heapRelation,
bool quiet, bool quiet,
bool concurrent) bool concurrent)
{ {
Oid *typeObjectId;
Oid *collationObjectId; Oid *collationObjectId;
Oid *classObjectId; Oid *classObjectId;
Oid accessMethodId; Oid accessMethodId;
...@@ -550,10 +540,12 @@ DefineIndex(RangeVar *heapRelation, ...@@ -550,10 +540,12 @@ DefineIndex(RangeVar *heapRelation,
indexInfo->ii_Concurrent = concurrent; indexInfo->ii_Concurrent = concurrent;
indexInfo->ii_BrokenHotChain = false; indexInfo->ii_BrokenHotChain = false;
typeObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
collationObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid)); collationObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid)); classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16)); coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
ComputeIndexAttrs(indexInfo, collationObjectId, classObjectId, ComputeIndexAttrs(indexInfo,
typeObjectId, collationObjectId, classObjectId,
coloptions, attributeList, coloptions, attributeList,
exclusionOpNames, relationId, exclusionOpNames, relationId,
accessMethodName, accessMethodId, accessMethodName, accessMethodId,
...@@ -980,6 +972,7 @@ CheckPredicate(Expr *predicate) ...@@ -980,6 +972,7 @@ CheckPredicate(Expr *predicate)
*/ */
static void static void
ComputeIndexAttrs(IndexInfo *indexInfo, ComputeIndexAttrs(IndexInfo *indexInfo,
Oid *typeOidP,
Oid *collationOidP, Oid *collationOidP,
Oid *classOidP, Oid *classOidP,
int16 *colOptionP, int16 *colOptionP,
...@@ -1108,6 +1101,8 @@ ComputeIndexAttrs(IndexInfo *indexInfo, ...@@ -1108,6 +1101,8 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
} }
} }
typeOidP[attn] = atttype;
/* /*
* Apply collation override if any * Apply collation override if any
*/ */
......
...@@ -1665,6 +1665,15 @@ where oid = 'test_storage'::regclass; ...@@ -1665,6 +1665,15 @@ where oid = 'test_storage'::regclass;
t t
(1 row) (1 row)
-- SET DATA TYPE without a rewrite
CREATE DOMAIN other_textarr AS text[];
CREATE TABLE norewrite_array(c text[] PRIMARY KEY);
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "norewrite_array_pkey" for table "norewrite_array"
SET client_min_messages = debug1;
ALTER TABLE norewrite_array ALTER c TYPE text[]; -- no work
ALTER TABLE norewrite_array ALTER c TYPE other_textarr; -- rebuild index
DEBUG: building index "norewrite_array_pkey" on table "norewrite_array"
RESET client_min_messages;
-- --
-- lock levels -- lock levels
-- --
......
...@@ -1197,6 +1197,14 @@ select reltoastrelid <> 0 as has_toast_table ...@@ -1197,6 +1197,14 @@ select reltoastrelid <> 0 as has_toast_table
from pg_class from pg_class
where oid = 'test_storage'::regclass; where oid = 'test_storage'::regclass;
-- SET DATA TYPE without a rewrite
CREATE DOMAIN other_textarr AS text[];
CREATE TABLE norewrite_array(c text[] PRIMARY KEY);
SET client_min_messages = debug1;
ALTER TABLE norewrite_array ALTER c TYPE text[]; -- no work
ALTER TABLE norewrite_array ALTER c TYPE other_textarr; -- rebuild index
RESET client_min_messages;
-- --
-- lock levels -- lock levels
-- --
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment