Skip to content

Commit 6eb71ac

Browse files
committed
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
1 parent 8366c78 commit 6eb71ac

File tree

3 files changed

+67
-55
lines changed

3 files changed

+67
-55
lines changed

src/backend/commands/indexcmds.c

Lines changed: 50 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "catalog/pg_opclass.h"
2424
#include "catalog/pg_opfamily.h"
2525
#include "catalog/pg_tablespace.h"
26+
#include "catalog/pg_type.h"
2627
#include "commands/dbcommands.h"
2728
#include "commands/defrem.h"
2829
#include "commands/tablecmds.h"
@@ -52,6 +53,7 @@
5253
/* non-export function prototypes */
5354
static void CheckPredicate(Expr *predicate);
5455
static void ComputeIndexAttrs(IndexInfo *indexInfo,
56+
Oid *typeOidP,
5557
Oid *collationOidP,
5658
Oid *classOidP,
5759
int16 *colOptionP,
@@ -87,18 +89,17 @@ static void RangeVarCallbackForReindexIndex(const RangeVar *relation,
8789
* of columns and that if one has an expression column or predicate, both do.
8890
* Errors arising from the attribute list still apply.
8991
*
90-
* Most column type changes that can skip a table rewrite will not invalidate
91-
* indexes. For btree and hash indexes, we assume continued validity when
92-
* each column of an index would have the same operator family before and
93-
* after the change. Since we do not document a contract for GIN or GiST
94-
* operator families, we require an exact operator class match for them and
95-
* for any other access methods.
96-
*
97-
* DefineIndex always verifies that each exclusion operator shares an operator
98-
* family with its corresponding index operator class. For access methods
99-
* having no operator family contract, confirm that the old and new indexes
100-
* use the exact same exclusion operator. For btree and hash, there's nothing
101-
* more to check.
92+
* Most column type changes that can skip a table rewrite do not invalidate
93+
* indexes. We ackowledge this when all operator classes, collations and
94+
* exclusion operators match. Though we could further permit intra-opfamily
95+
* changes for btree and hash indexes, that adds subtle complexity with no
96+
* concrete benefit for core types.
97+
98+
* When a comparison or exclusion operator has a polymorphic input type, the
99+
* actual input types must also match. This defends against the possibility
100+
* that operators could vary behavior in response to get_fn_expr_argtype().
101+
* At present, this hazard is theoretical: check_exclusion_constraint() and
102+
* all core index access methods decline to set fn_expr for such calls.
102103
*
103104
* We do not yet implement a test to verify compatibility of expression
104105
* columns or predicates, so assume any such index is incompatible.
@@ -111,6 +112,7 @@ CheckIndexCompatible(Oid oldId,
111112
List *exclusionOpNames)
112113
{
113114
bool isconstraint;
115+
Oid *typeObjectId;
114116
Oid *collationObjectId;
115117
Oid *classObjectId;
116118
Oid accessMethodId;
@@ -123,10 +125,10 @@ CheckIndexCompatible(Oid oldId,
123125
int numberOfAttributes;
124126
int old_natts;
125127
bool isnull;
126-
bool family_am;
127128
bool ret = true;
128129
oidvector *old_indclass;
129130
oidvector *old_indcollation;
131+
Relation irel;
130132
int i;
131133
Datum d;
132134

@@ -168,10 +170,12 @@ CheckIndexCompatible(Oid oldId,
168170
indexInfo->ii_ExclusionOps = NULL;
169171
indexInfo->ii_ExclusionProcs = NULL;
170172
indexInfo->ii_ExclusionStrats = NULL;
173+
typeObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
171174
collationObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
172175
classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
173176
coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
174-
ComputeIndexAttrs(indexInfo, collationObjectId, classObjectId,
177+
ComputeIndexAttrs(indexInfo,
178+
typeObjectId, collationObjectId, classObjectId,
175179
coloptions, attributeList,
176180
exclusionOpNames, relationId,
177181
accessMethodName, accessMethodId,
@@ -191,12 +195,7 @@ CheckIndexCompatible(Oid oldId,
191195
return false;
192196
}
193197

194-
/*
195-
* If the old and new operator class of any index column differ in
196-
* operator family or collation, regard the old index as incompatible.
197-
* For access methods other than btree and hash, a family match has no
198-
* defined meaning; require an exact operator class match.
199-
*/
198+
/* Any change in operator class or collation breaks compatibility. */
200199
old_natts = ((Form_pg_index) GETSTRUCT(tuple))->indnatts;
201200
Assert(old_natts == numberOfAttributes);
202201

@@ -208,52 +207,42 @@ CheckIndexCompatible(Oid oldId,
208207
Assert(!isnull);
209208
old_indclass = (oidvector *) DatumGetPointer(d);
210209

211-
family_am = accessMethodId == BTREE_AM_OID || accessMethodId == HASH_AM_OID;
212-
213-
for (i = 0; i < old_natts; i++)
214-
{
215-
Oid old_class = old_indclass->values[i];
216-
Oid new_class = classObjectId[i];
217-
218-
if (!(old_indcollation->values[i] == collationObjectId[i]
219-
&& (old_class == new_class
220-
|| (family_am && (get_opclass_family(old_class)
221-
== get_opclass_family(new_class))))))
222-
{
223-
ret = false;
224-
break;
225-
}
226-
}
210+
ret = (memcmp(old_indclass->values, classObjectId,
211+
old_natts * sizeof(Oid)) == 0 &&
212+
memcmp(old_indcollation->values, collationObjectId,
213+
old_natts * sizeof(Oid)) == 0);
227214

228215
ReleaseSysCache(tuple);
229216

230-
/*
231-
* For btree and hash, exclusion operators need only fall in the same
232-
* operator family; ComputeIndexAttrs already verified that much. If we
233-
* get this far, we know that the index operator family has not changed,
234-
* and we're done. For other access methods, require exact matches for
235-
* all exclusion operators.
236-
*/
237-
if (ret && !family_am && indexInfo->ii_ExclusionOps != NULL)
217+
/* For polymorphic opcintype, column type changes break compatibility. */
218+
irel = index_open(oldId, AccessShareLock); /* caller probably has a lock */
219+
for (i = 0; i < old_natts && ret; i++)
220+
ret = (!IsPolymorphicType(get_opclass_input_type(classObjectId[i])) ||
221+
irel->rd_att->attrs[i]->atttypid == typeObjectId[i]);
222+
223+
/* Any change in exclusion operator selections breaks compatibility. */
224+
if (ret && indexInfo->ii_ExclusionOps != NULL)
238225
{
239-
Relation irel;
240226
Oid *old_operators, *old_procs;
241227
uint16 *old_strats;
242228

243-
/* Caller probably already holds a stronger lock. */
244-
irel = index_open(oldId, AccessShareLock);
245229
RelationGetExclusionInfo(irel, &old_operators, &old_procs, &old_strats);
230+
ret = memcmp(old_operators, indexInfo->ii_ExclusionOps,
231+
old_natts * sizeof(Oid)) == 0;
246232

247-
for (i = 0; i < old_natts; i++)
248-
if (old_operators[i] != indexInfo->ii_ExclusionOps[i])
249-
{
250-
ret = false;
251-
break;
252-
}
233+
/* Require an exact input type match for polymorphic operators. */
234+
for (i = 0; i < old_natts && ret; i++)
235+
{
236+
Oid left,
237+
right;
253238

254-
index_close(irel, NoLock);
239+
op_input_types(indexInfo->ii_ExclusionOps[i], &left, &right);
240+
ret = (!(IsPolymorphicType(left) || IsPolymorphicType(right)) ||
241+
irel->rd_att->attrs[i]->atttypid == typeObjectId[i]);
242+
}
255243
}
256244

245+
index_close(irel, NoLock);
257246
return ret;
258247
}
259248

@@ -315,6 +304,7 @@ DefineIndex(RangeVar *heapRelation,
315304
bool quiet,
316305
bool concurrent)
317306
{
307+
Oid *typeObjectId;
318308
Oid *collationObjectId;
319309
Oid *classObjectId;
320310
Oid accessMethodId;
@@ -550,10 +540,12 @@ DefineIndex(RangeVar *heapRelation,
550540
indexInfo->ii_Concurrent = concurrent;
551541
indexInfo->ii_BrokenHotChain = false;
552542

543+
typeObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
553544
collationObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
554545
classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
555546
coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
556-
ComputeIndexAttrs(indexInfo, collationObjectId, classObjectId,
547+
ComputeIndexAttrs(indexInfo,
548+
typeObjectId, collationObjectId, classObjectId,
557549
coloptions, attributeList,
558550
exclusionOpNames, relationId,
559551
accessMethodName, accessMethodId,
@@ -980,6 +972,7 @@ CheckPredicate(Expr *predicate)
980972
*/
981973
static void
982974
ComputeIndexAttrs(IndexInfo *indexInfo,
975+
Oid *typeOidP,
983976
Oid *collationOidP,
984977
Oid *classOidP,
985978
int16 *colOptionP,
@@ -1108,6 +1101,8 @@ ComputeIndexAttrs(IndexInfo *indexInfo,
11081101
}
11091102
}
11101103

1104+
typeOidP[attn] = atttype;
1105+
11111106
/*
11121107
* Apply collation override if any
11131108
*/

src/test/regress/expected/alter_table.out

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1665,6 +1665,15 @@ where oid = 'test_storage'::regclass;
16651665
t
16661666
(1 row)
16671667

1668+
-- SET DATA TYPE without a rewrite
1669+
CREATE DOMAIN other_textarr AS text[];
1670+
CREATE TABLE norewrite_array(c text[] PRIMARY KEY);
1671+
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "norewrite_array_pkey" for table "norewrite_array"
1672+
SET client_min_messages = debug1;
1673+
ALTER TABLE norewrite_array ALTER c TYPE text[]; -- no work
1674+
ALTER TABLE norewrite_array ALTER c TYPE other_textarr; -- rebuild index
1675+
DEBUG: building index "norewrite_array_pkey" on table "norewrite_array"
1676+
RESET client_min_messages;
16681677
--
16691678
-- lock levels
16701679
--

src/test/regress/sql/alter_table.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,6 +1197,14 @@ select reltoastrelid <> 0 as has_toast_table
11971197
from pg_class
11981198
where oid = 'test_storage'::regclass;
11991199

1200+
-- SET DATA TYPE without a rewrite
1201+
CREATE DOMAIN other_textarr AS text[];
1202+
CREATE TABLE norewrite_array(c text[] PRIMARY KEY);
1203+
SET client_min_messages = debug1;
1204+
ALTER TABLE norewrite_array ALTER c TYPE text[]; -- no work
1205+
ALTER TABLE norewrite_array ALTER c TYPE other_textarr; -- rebuild index
1206+
RESET client_min_messages;
1207+
12001208
--
12011209
-- lock levels
12021210
--

0 commit comments

Comments
 (0)