mirror of
https://github.com/postgres/postgres.git
synced 2025-06-22 02:52:08 +03:00
Avoid index rebuild for no-rewrite ALTER TABLE .. ALTER TYPE.
Noah Misch. Review and minor cosmetic changes by me.
This commit is contained in:
@ -72,6 +72,198 @@ static Oid GetIndexOpClass(List *opclass, Oid attrType,
|
||||
static char *ChooseIndexNameAddition(List *colnames);
|
||||
|
||||
|
||||
/*
|
||||
* CheckIndexCompatible
|
||||
* Determine whether an existing index definition is compatible with a
|
||||
* prospective index definition, such that the existing index storage
|
||||
* could become the storage of the new index, avoiding a rebuild.
|
||||
*
|
||||
* 'heapRelation': the relation the index would apply to.
|
||||
* 'accessMethodName': name of the AM to use.
|
||||
* 'attributeList': a list of IndexElem specifying columns and expressions
|
||||
* to index on.
|
||||
* 'exclusionOpNames': list of names of exclusion-constraint operators,
|
||||
* or NIL if not an exclusion constraint.
|
||||
*
|
||||
* This is tailored to the needs of ALTER TABLE ALTER TYPE, which recreates
|
||||
* any indexes that depended on a changing column from their pg_get_indexdef
|
||||
* or pg_get_constraintdef definitions. We omit some of the sanity checks of
|
||||
* DefineIndex. We assume that the old and new indexes have the same number
|
||||
* of columns and that if one has an expression column or predicate, both do.
|
||||
* Errors arising from the attribute list still apply.
|
||||
*
|
||||
* Most column type changes that can skip a table rewrite will not invalidate
|
||||
* indexes. For btree and hash indexes, we assume continued validity when
|
||||
* each column of an index would have the same operator family before and
|
||||
* after the change. Since we do not document a contract for GIN or GiST
|
||||
* operator families, we require an exact operator class match for them and
|
||||
* for any other access methods.
|
||||
*
|
||||
* DefineIndex always verifies that each exclusion operator shares an operator
|
||||
* family with its corresponding index operator class. For access methods
|
||||
* having no operator family contract, confirm that the old and new indexes
|
||||
* use the exact same exclusion operator. For btree and hash, there's nothing
|
||||
* more to check.
|
||||
*
|
||||
* We do not yet implement a test to verify compatibility of expression
|
||||
* columns or predicates, so assume any such index is incompatible.
|
||||
*/
|
||||
bool
|
||||
CheckIndexCompatible(Oid oldId,
|
||||
RangeVar *heapRelation,
|
||||
char *accessMethodName,
|
||||
List *attributeList,
|
||||
List *exclusionOpNames)
|
||||
{
|
||||
bool isconstraint;
|
||||
Oid *collationObjectId;
|
||||
Oid *classObjectId;
|
||||
Oid accessMethodId;
|
||||
Oid relationId;
|
||||
HeapTuple tuple;
|
||||
Form_pg_am accessMethodForm;
|
||||
bool amcanorder;
|
||||
RegProcedure amoptions;
|
||||
int16 *coloptions;
|
||||
IndexInfo *indexInfo;
|
||||
int numberOfAttributes;
|
||||
int old_natts;
|
||||
bool isnull;
|
||||
bool family_am;
|
||||
bool ret = true;
|
||||
oidvector *old_indclass;
|
||||
oidvector *old_indcollation;
|
||||
int i;
|
||||
Datum d;
|
||||
|
||||
/* Caller should already have the relation locked in some way. */
|
||||
relationId = RangeVarGetRelid(heapRelation, NoLock, false, false);
|
||||
/*
|
||||
* We can pretend isconstraint = false unconditionally. It only serves to
|
||||
* decide the text of an error message that should never happen for us.
|
||||
*/
|
||||
isconstraint = false;
|
||||
|
||||
numberOfAttributes = list_length(attributeList);
|
||||
Assert(numberOfAttributes > 0);
|
||||
Assert(numberOfAttributes <= INDEX_MAX_KEYS);
|
||||
|
||||
/* look up the access method */
|
||||
tuple = SearchSysCache1(AMNAME, PointerGetDatum(accessMethodName));
|
||||
if (!HeapTupleIsValid(tuple))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
errmsg("access method \"%s\" does not exist",
|
||||
accessMethodName)));
|
||||
accessMethodId = HeapTupleGetOid(tuple);
|
||||
accessMethodForm = (Form_pg_am) GETSTRUCT(tuple);
|
||||
amcanorder = accessMethodForm->amcanorder;
|
||||
amoptions = accessMethodForm->amoptions;
|
||||
ReleaseSysCache(tuple);
|
||||
|
||||
/*
|
||||
* Compute the operator classes, collations, and exclusion operators
|
||||
* for the new index, so we can test whether it's compatible with the
|
||||
* existing one. Note that ComputeIndexAttrs might fail here, but that's
|
||||
* OK: DefineIndex would have called this function with the same arguments
|
||||
* later on, and it would have failed then anyway.
|
||||
*/
|
||||
indexInfo = makeNode(IndexInfo);
|
||||
indexInfo->ii_Expressions = NIL;
|
||||
indexInfo->ii_ExpressionsState = NIL;
|
||||
indexInfo->ii_PredicateState = NIL;
|
||||
indexInfo->ii_ExclusionOps = NULL;
|
||||
indexInfo->ii_ExclusionProcs = NULL;
|
||||
indexInfo->ii_ExclusionStrats = NULL;
|
||||
collationObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
|
||||
classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
|
||||
coloptions = (int16 *) palloc(numberOfAttributes * sizeof(int16));
|
||||
ComputeIndexAttrs(indexInfo, collationObjectId, classObjectId,
|
||||
coloptions, attributeList,
|
||||
exclusionOpNames, relationId,
|
||||
accessMethodName, accessMethodId,
|
||||
amcanorder, isconstraint);
|
||||
|
||||
|
||||
/* Get the soon-obsolete pg_index tuple. */
|
||||
tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(oldId));
|
||||
if (!HeapTupleIsValid(tuple))
|
||||
elog(ERROR, "cache lookup failed for index %u", oldId);
|
||||
|
||||
/* We don't assess expressions or predicates; assume incompatibility. */
|
||||
if (!(heap_attisnull(tuple, Anum_pg_index_indpred) &&
|
||||
heap_attisnull(tuple, Anum_pg_index_indexprs)))
|
||||
{
|
||||
ReleaseSysCache(tuple);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
Assert(old_natts == numberOfAttributes);
|
||||
|
||||
d = SysCacheGetAttr(INDEXRELID, tuple, Anum_pg_index_indcollation, &isnull);
|
||||
Assert(!isnull);
|
||||
old_indcollation = (oidvector *) DatumGetPointer(d);
|
||||
|
||||
d = SysCacheGetAttr(INDEXRELID, tuple, Anum_pg_index_indclass, &isnull);
|
||||
Assert(!isnull);
|
||||
old_indclass = (oidvector *) DatumGetPointer(d);
|
||||
|
||||
family_am = accessMethodId == BTREE_AM_OID || accessMethodId == HASH_AM_OID;
|
||||
|
||||
for (i = 0; i < old_natts; i++)
|
||||
{
|
||||
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);
|
||||
|
||||
/*
|
||||
* For btree and hash, exclusion operators need only fall in the same
|
||||
* operator family; ComputeIndexAttrs already verified that much. If we
|
||||
* get this far, we know that the index operator family has not changed,
|
||||
* and we're done. For other access methods, require exact matches for
|
||||
* all exclusion operators.
|
||||
*/
|
||||
if (ret && !family_am && indexInfo->ii_ExclusionOps != NULL)
|
||||
{
|
||||
Relation irel;
|
||||
Oid *old_operators, *old_procs;
|
||||
uint16 *old_strats;
|
||||
|
||||
/* Caller probably already holds a stronger lock. */
|
||||
irel = index_open(oldId, AccessShareLock);
|
||||
RelationGetExclusionInfo(irel, &old_operators, &old_procs, &old_strats);
|
||||
|
||||
for (i = 0; i < old_natts; i++)
|
||||
if (old_operators[i] != indexInfo->ii_ExclusionOps[i])
|
||||
{
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
|
||||
index_close(irel, NoLock);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* DefineIndex
|
||||
* Creates a new index.
|
||||
@ -81,6 +273,8 @@ static char *ChooseIndexNameAddition(List *colnames);
|
||||
* that a nonconflicting default name should be picked.
|
||||
* 'indexRelationId': normally InvalidOid, but during bootstrap can be
|
||||
* nonzero to specify a preselected OID for the index.
|
||||
* 'relFileNode': normally InvalidOid, but can be nonzero to specify existing
|
||||
* storage constituting a valid build of this index.
|
||||
* 'accessMethodName': name of the AM to use.
|
||||
* 'tableSpaceName': name of the tablespace to create the index in.
|
||||
* NULL specifies using the appropriate default.
|
||||
@ -103,11 +297,14 @@ static char *ChooseIndexNameAddition(List *colnames);
|
||||
* it will be filled later.
|
||||
* 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
|
||||
* 'concurrent': avoid blocking writers to the table while building.
|
||||
*
|
||||
* Returns the OID of the created index.
|
||||
*/
|
||||
void
|
||||
Oid
|
||||
DefineIndex(RangeVar *heapRelation,
|
||||
char *indexRelationName,
|
||||
Oid indexRelationId,
|
||||
Oid relFileNode,
|
||||
char *accessMethodName,
|
||||
char *tableSpaceName,
|
||||
List *attributeList,
|
||||
@ -402,12 +599,18 @@ DefineIndex(RangeVar *heapRelation,
|
||||
indexRelationName, RelationGetRelationName(rel))));
|
||||
}
|
||||
|
||||
/*
|
||||
* A valid relFileNode implies that we already have a built form of the
|
||||
* index. The caller should also decline any index build.
|
||||
*/
|
||||
Assert(!OidIsValid(relFileNode) || (skip_build && !concurrent));
|
||||
|
||||
/*
|
||||
* Make the catalog entries for the index, including constraints. Then, if
|
||||
* not skip_build || concurrent, actually build the index.
|
||||
*/
|
||||
indexRelationId =
|
||||
index_create(rel, indexRelationName, indexRelationId,
|
||||
index_create(rel, indexRelationName, indexRelationId, relFileNode,
|
||||
indexInfo, indexColNames,
|
||||
accessMethodId, tablespaceId,
|
||||
collationObjectId, classObjectId,
|
||||
@ -421,7 +624,7 @@ DefineIndex(RangeVar *heapRelation,
|
||||
{
|
||||
/* Close the heap and we're done, in the non-concurrent case */
|
||||
heap_close(rel, NoLock);
|
||||
return;
|
||||
return indexRelationId;
|
||||
}
|
||||
|
||||
/* save lockrelid and locktag for below, then close rel */
|
||||
@ -709,6 +912,8 @@ DefineIndex(RangeVar *heapRelation,
|
||||
* Last thing to do is release the session-level lock on the parent table.
|
||||
*/
|
||||
UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
|
||||
|
||||
return indexRelationId;
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user