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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy