Previous version (at7-alt-index-opfamily.patch):
http://archives.postgresql.org/message-id/20110113230124.ga18...@tornado.gateway.2wire.net
Design:
http://archives.postgresql.org/message-id/20110524104029.gb18...@tornado.gateway.2wire.net

Patches committed in 2011CF1 allow ALTER TABLE ... ALTER ... SET DATA TYPE to
avoid rewriting the table in certain cases.  We still rebuild all indexes and
recheck all foreign key constraints dependent on the changing column.  This
patch avoids the rebuild for some indexes, using the operator family facility
to identify when that is safe.

Changes since last version:

* Instead of noting in comments that the operator family contracts do not make
the guarantees I desire and opining that the code is fine in practice anyway,
I amend those contracts in the documentation.  See the aforementioned design
explanation, but note that its third and fifth sentences are incorrect.  I
settled on requiring identical behavior among operators in the family after
implicit casts (of any castmethod) or binary coercion casts (of any
castcontext) on operands.  I really only need the requirement for binary
coercion casts, but implicit casts seem like a obvious extension.

* Update for per-column collations.  Index collations need to match.

* We don't assign meaning to GIN or GiST operator family groupings.  For
access methods other than btree and hash, require an index rebuild unless the
operator classes match exactly.  Even if we could do otherwise, it would
currently be academic; GIN "array_ops" is the only such family with more than
one constituent operator class.  The only practical case I've observed to be
helped here is a conversion like varchar(2)[] -> varchar(4)[] with a GIN index
on the column; operator class comparison covers that fine.

* Remove support for avoiding foreign key constraint revalidation.  This is
orthogonal to the index case, though they share many principles.  If we get
this committed, I will submit a small patch for foreign keys at a later date.

* Original patch was submitted in two forms for comment on the relative
merits.  The first form had tablecmds.c updating catalog entries pertaining to
an old index until it matched how the index would be recreated with the new
column data type.  The second form actually dropped and recreated the index
using the usual facilities, then reattached the old RelFileNode to the "new"
index.  Nobody commented, but I've come around to thinking the second approach
is clearly better.  Therefore, I'm submitting only that.

* Change the division of labor between tablecmds.c and indexcmds.c.
* Test cases removed per 2011CF1 discussion.
* Sync with variable names changed since last submission.

This patch is fully independent of the "Eliding no-op varchar length
coercions" patch I've posted recently, though they do have synergy.

Thanks,
nm
diff --git a/doc/src/sgml/xindex.sgml b/doc/src/sgml/xindex.sgml
index a90b4e2..b6fe908 100644
*** a/doc/src/sgml/xindex.sgml
--- b/doc/src/sgml/xindex.sgml
***************
*** 834,846 **** ALTER OPERATOR FAMILY integer_ops USING btree ADD
    <para>
     In a B-tree operator family, all the operators in the family must sort
     compatibly, meaning that the transitive laws hold across all the data types
!    supported by the family: <quote>if A = B and B = C, then A =
!    C</>, and <quote>if A &lt; B and B &lt; C, then A &lt; C</>.  For each
!    operator in the family there must be a support function having the same
!    two input data types as the operator.  It is recommended that a family be
!    complete, i.e., for each combination of data types, all operators are
!    included.  Each operator class should include just the non-cross-type
!    operators and support function for its data type.
    </para>
  
    <para>
--- 834,848 ----
    <para>
     In a B-tree operator family, all the operators in the family must sort
     compatibly, meaning that the transitive laws hold across all the data types
!    supported by the family: <quote>if A = B and B = C, then A = C</>,
!    and <quote>if A &lt; B and B &lt; C, then A &lt; C</>.  Subjecting operands
!    to any number of implicit casts or binary coercion casts involving only
!    types represented in the family must not change the result other than to
!    require a similar cast thereon.  For each operator in the family there must
!    be a support function having the same two input data types as the operator.
!    It is recommended that a family be complete, i.e., for each combination of
!    data types, all operators are included.  Each operator class should include
!    just the non-cross-type operators and support function for its data type.
    </para>
  
    <para>
***************
*** 851,861 **** ALTER OPERATOR FAMILY integer_ops USING btree ADD
     by the family's equality operators, even when the values are of different
     types.  This is usually difficult to accomplish when the types have
     different physical representations, but it can be done in some cases.
!    Notice that there is only one support function per data type, not one
!    per equality operator.  It is recommended that a family be complete, i.e.,
!    provide an equality operator for each combination of data types.
!    Each operator class should include just the non-cross-type equality
!    operator and the support function for its data type.
    </para>
  
    <para>
--- 853,865 ----
     by the family's equality operators, even when the values are of different
     types.  This is usually difficult to accomplish when the types have
     different physical representations, but it can be done in some cases.
!    Implicit casts and binary coercion casts among types represented in the
!    family must preserve this invariant.  Notice that there is only one support
!    function per data type, not one per equality operator.  It is recommended
!    that a family be complete, i.e., provide an equality operator for each
!    combination of data types.  Each operator class should include just the
!    non-cross-type equality operator and the support function for its data
!    type.
    </para>
  
    <para>
diff --git a/src/backend/cataloindex 39ba486..6fc234b 100644
diff --git a/src/backend/catalog/sindex 57987be..682bd89 100644
*** a/src/backend/catalog/storage.c
--- b/src/backend/catalog/storage.c
***************
*** 221,229 **** RelationPreserveStorage(RelFileNode rnode)
                next = pending->next;
                if (RelFileNodeEquals(rnode, pending->relnode))
                {
-                       /* we should only find delete-on-abort entries, else 
trouble */
-                       if (pending->atCommit)
-                               elog(ERROR, "cannot preserve a delete-on-commit 
relation");
                        /* unlink and delete list entry */
                        if (prev)
                                prev->next = next;
--- 221,226 ----
diff --git a/src/backend/commands/inindex b7c021d..073a3fa 100644
*** a/src/backend/commands/indexcmds.c
--- b/src/backend/commands/indexcmds.c
***************
*** 73,78 **** static char *ChooseIndexNameAddition(List *colnames);
--- 73,264 ----
  
  
  /*
+  * 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, 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);
+ 
+       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.
   *
***************
*** 103,110 **** 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.
   */
! void
  DefineIndex(RangeVar *heapRelation,
                        char *indexRelationName,
                        Oid indexRelationId,
--- 289,298 ----
   *            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 new index relation.
   */
! Oid
  DefineIndex(RangeVar *heapRelation,
                        char *indexRelationName,
                        Oid indexRelationId,
***************
*** 421,427 **** DefineIndex(RangeVar *heapRelation,
        {
                /* Close the heap and we're done, in the non-concurrent case */
                heap_close(rel, NoLock);
!               return;
        }
  
        /* save lockrelid and locktag for below, then close rel */
--- 609,615 ----
        {
                /* Close the heap and we're done, in the non-concurrent case */
                heap_close(rel, NoLock);
!               return indexRelationId;
        }
  
        /* save lockrelid and locktag for below, then close rel */
***************
*** 709,714 **** DefineIndex(RangeVar *heapRelation,
--- 897,904 ----
         * Last thing to do is release the session-level lock on the parent 
table.
         */
        UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
+ 
+       return indexRelationId;
  }
  
  
diff --git a/src/backend/commands/tableindex 2c9f855..3860e61 100644
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 344,350 **** static bool ATColumnChangeRequiresRewrite(Node *expr, 
AttrNumber varattno);
  static void ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
                                          AlterTableCmd *cmd, LOCKMODE 
lockmode);
  static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, 
LOCKMODE lockmode);
! static void ATPostAlterTypeParse(char *cmd, List **wqueue, LOCKMODE lockmode);
  static void change_owner_recurse_to_sequences(Oid relationOid,
                                                                  Oid 
newOwnerId, LOCKMODE lockmode);
  static void ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE 
lockmode);
--- 344,352 ----
  static void ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
                                          AlterTableCmd *cmd, LOCKMODE 
lockmode);
  static void ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, 
LOCKMODE lockmode);
! static void ATPostAlterTypeParse(Oid oldId, char *cmd,
!                                        List **wqueue, LOCKMODE lockmode, bool 
rewrite);
! static void TryReuseIndex(Oid oldId, IndexStmt *stmt);
  static void change_owner_recurse_to_sequences(Oid relationOid,
                                                                  Oid 
newOwnerId, LOCKMODE lockmode);
  static void ATExecClusterOn(Relation rel, const char *indexName, LOCKMODE 
lockmode);
***************
*** 5179,5215 **** ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
        bool            check_rights;
        bool            skip_build;
        bool            quiet;
  
        Assert(IsA(stmt, IndexStmt));
  
        /* suppress schema rights check when rebuilding existing index */
        check_rights = !is_rebuild;
!       /* skip index build if phase 3 will have to rewrite table anyway */
!       skip_build = tab->rewrite;
        /* suppress notices when rebuilding existing index */
        quiet = is_rebuild;
  
        /* The IndexStmt has already been through transformIndexStmt */
  
!       DefineIndex(stmt->relation, /* relation */
!                               stmt->idxname,  /* index name */
!                               InvalidOid,             /* no predefined OID */
!                               stmt->accessMethod,             /* am name */
!                               stmt->tableSpace,
!                               stmt->indexParams,              /* parameters */
!                               (Expr *) stmt->whereClause,
!                               stmt->options,
!                               stmt->excludeOpNames,
!                               stmt->unique,
!                               stmt->primary,
!                               stmt->isconstraint,
!                               stmt->deferrable,
!                               stmt->initdeferred,
!                               true,                   /* is_alter_table */
!                               check_rights,
!                               skip_build,
!                               quiet,
!                               false);
  }
  
  /*
--- 5181,5255 ----
        bool            check_rights;
        bool            skip_build;
        bool            quiet;
+       Oid                     new_index;
  
        Assert(IsA(stmt, IndexStmt));
  
        /* suppress schema rights check when rebuilding existing index */
        check_rights = !is_rebuild;
!       /* skip index build if phase 3 will do it or we're reusing an old one */
!       skip_build = tab->rewrite || OidIsValid(stmt->oldNode);
        /* suppress notices when rebuilding existing index */
        quiet = is_rebuild;
  
        /* The IndexStmt has already been through transformIndexStmt */
  
!       new_index = DefineIndex(stmt->relation,         /* relation */
!                                                       stmt->idxname,          
/* index name */
!                                                       InvalidOid, /* no 
predefined OID */
!                                                       stmt->accessMethod, /* 
am name */
!                                                       stmt->tableSpace,
!                                                       stmt->indexParams,      
/* parameters */
!                                                       (Expr *) 
stmt->whereClause,
!                                                       stmt->options,
!                                                       stmt->excludeOpNames,
!                                                       stmt->unique,
!                                                       stmt->primary,
!                                                       stmt->isconstraint,
!                                                       stmt->deferrable,
!                                                       stmt->initdeferred,
!                                                       true,           /* 
is_alter_table */
!                                                       check_rights,
!                                                       skip_build,
!                                                       quiet,
!                                                       false);
! 
!       /*
!        * If TryReuseIndex() stashed a relfilenode for us, replace the fresh 
one
!        * with that.  This vaguely follows swap_relation_files(), but we need 
not
!        * consider the case of a system table.  Indexes inherit relfrozenxid 
and
!        * never have TOAST tables.
!        */
!       if (OidIsValid(stmt->oldNode))
!       {
!               Relation        pg_class;
!               Relation        irel;
!               HeapTuple       tup;
! 
!               /* Drop the empty storage just created under DefineIndex. */
!               irel = index_open(new_index, NoLock);
!               RelationDropStorage(irel);
! 
!               /* Update pg_class.relfilenode. */
!               pg_class = heap_open(RelationRelationId, RowExclusiveLock);
!               tup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(new_index));
!               if (!HeapTupleIsValid(tup))             /* shouldn't happen */
!                       elog(ERROR, "cache lookup failed for relation %u", 
new_index);
! 
!               ((Form_pg_class) GETSTRUCT(tup))->relfilenode = stmt->oldNode;
!               simple_heap_update(pg_class, &tup->t_self, tup);
!               CatalogUpdateIndexes(pg_class, tup);
!               heap_close(pg_class, RowExclusiveLock);
! 
!               CommandCounterIncrement();
! 
!               /*
!                * The reassigned storage is probably scheduled for deletion, 
having
!                * been attached to a just-DROPped index.  Revive it.
!                */
!               RelationPreserveStorage(irel->rd_node);
!               index_close(irel, NoLock);
!       }
  }
  
  /*
***************
*** 7171,7177 **** static void
  ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE 
lockmode)
  {
        ObjectAddress obj;
!       ListCell   *l;
  
        /*
         * Re-parse the index and constraint definitions, and attach them to the
--- 7211,7218 ----
  ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, LOCKMODE 
lockmode)
  {
        ObjectAddress obj;
!       ListCell   *def;
!       ListCell   *oid;
  
        /*
         * Re-parse the index and constraint definitions, and attach them to the
***************
*** 7181,7190 **** ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, 
LOCKMODE lockmode)
         * that before dropping.  It's safe because the parser won't actually 
look
         * at the catalogs to detect the existing entry.
         */
!       foreach(l, tab->changedIndexDefs)
!               ATPostAlterTypeParse((char *) lfirst(l), wqueue, lockmode);
!       foreach(l, tab->changedConstraintDefs)
!               ATPostAlterTypeParse((char *) lfirst(l), wqueue, lockmode);
  
        /*
         * Now we can drop the existing constraints and indexes --- constraints
--- 7222,7233 ----
         * that before dropping.  It's safe because the parser won't actually 
look
         * at the catalogs to detect the existing entry.
         */
!       forboth(oid, tab->changedConstraintOids, def, 
tab->changedConstraintDefs)
!               ATPostAlterTypeParse(lfirst_oid(oid), (char *) lfirst(def),
!                                                        wqueue, lockmode, 
tab->rewrite);
!       forboth(oid, tab->changedIndexOids, def, tab->changedIndexDefs)
!               ATPostAlterTypeParse(lfirst_oid(oid), (char *) lfirst(def),
!                                                        wqueue, lockmode, 
tab->rewrite);
  
        /*
         * Now we can drop the existing constraints and indexes --- constraints
***************
*** 7194,7211 **** ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, 
LOCKMODE lockmode)
         * should be okay to use DROP_RESTRICT here, since nothing else should 
be
         * depending on these objects.
         */
!       foreach(l, tab->changedConstraintOids)
        {
                obj.classId = ConstraintRelationId;
!               obj.objectId = lfirst_oid(l);
                obj.objectSubId = 0;
                performDeletion(&obj, DROP_RESTRICT);
        }
  
!       foreach(l, tab->changedIndexOids)
        {
                obj.classId = RelationRelationId;
!               obj.objectId = lfirst_oid(l);
                obj.objectSubId = 0;
                performDeletion(&obj, DROP_RESTRICT);
        }
--- 7237,7254 ----
         * should be okay to use DROP_RESTRICT here, since nothing else should 
be
         * depending on these objects.
         */
!       foreach(oid, tab->changedConstraintOids)
        {
                obj.classId = ConstraintRelationId;
!               obj.objectId = lfirst_oid(oid);
                obj.objectSubId = 0;
                performDeletion(&obj, DROP_RESTRICT);
        }
  
!       foreach(oid, tab->changedIndexOids)
        {
                obj.classId = RelationRelationId;
!               obj.objectId = lfirst_oid(oid);
                obj.objectSubId = 0;
                performDeletion(&obj, DROP_RESTRICT);
        }
***************
*** 7217,7223 **** ATPostAlterTypeCleanup(List **wqueue, AlteredTableInfo *tab, 
LOCKMODE lockmode)
  }
  
  static void
! ATPostAlterTypeParse(char *cmd, List **wqueue, LOCKMODE lockmode)
  {
        List       *raw_parsetree_list;
        List       *querytree_list;
--- 7260,7267 ----
  }
  
  static void
! ATPostAlterTypeParse(Oid oldId, char *cmd,
!                                        List **wqueue, LOCKMODE lockmode, bool 
rewrite)
  {
        List       *raw_parsetree_list;
        List       *querytree_list;
***************
*** 7264,7269 **** ATPostAlterTypeParse(char *cmd, List **wqueue, LOCKMODE 
lockmode)
--- 7308,7316 ----
                                        IndexStmt  *stmt = (IndexStmt *) stm;
                                        AlterTableCmd *newcmd;
  
+                                       if (!rewrite)
+                                               TryReuseIndex(oldId, stmt);
+ 
                                        rel = relation_openrv(stmt->relation, 
lockmode);
                                        tab = ATGetQueueEntry(wqueue, rel);
                                        newcmd = makeNode(AlterTableCmd);
***************
*** 7288,7293 **** ATPostAlterTypeParse(char *cmd, List **wqueue, LOCKMODE 
lockmode)
--- 7335,7344 ----
                                                switch (cmd->subtype)
                                                {
                                                        case AT_AddIndex:
+                                                               
Assert(IsA(cmd->def, IndexStmt));
+                                                               if (!rewrite)
+                                                                       
TryReuseIndex(get_constraint_index(oldId),
+                                                                               
                  (IndexStmt *) cmd->def);
                                                                cmd->subtype = 
AT_ReAddIndex;
                                                                
tab->subcmds[AT_PASS_OLD_INDEX] =
                                                                        
lappend(tab->subcmds[AT_PASS_OLD_INDEX], cmd);
***************
*** 7311,7316 **** ATPostAlterTypeParse(char *cmd, List **wqueue, LOCKMODE 
lockmode)
--- 7362,7387 ----
        }
  }
  
+ /*
+  * Subroutine for ATPostAlterTypeParse().  Calls out to CheckIndexCompatible()
+  * for the real analysis, then mutates the IndexStmt based on that verdict.
+ */
+ static void
+ TryReuseIndex(Oid oldId, IndexStmt *stmt)
+ {
+ 
+       if (CheckIndexCompatible(oldId,
+                                                        stmt->relation,
+                                                        stmt->accessMethod,
+                                                        stmt->indexParams,
+                                                        stmt->excludeOpNames))
+       {
+               Relation irel = index_open(oldId, NoLock);
+               stmt->oldNode = irel->rd_node.relNode;
+               index_close(irel, NoLock);
+       }
+ }
+ 
  
  /*
   * ALTER TABLE OWNER
diff --git a/src/backend/nodes/copyfuncindex c9133dd..c18d670 100644
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 2813,2818 **** _copyIndexStmt(IndexStmt *from)
--- 2813,2819 ----
        COPY_SCALAR_FIELD(deferrable);
        COPY_SCALAR_FIELD(initdeferred);
        COPY_SCALAR_FIELD(concurrent);
+       COPY_SCALAR_FIELD(oldNode);
  
        return newnode;
  }
diff --git a/src/backend/nodes/equalindex 3a0267c..61fed33 100644
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 1251,1256 **** _equalIndexStmt(IndexStmt *a, IndexStmt *b)
--- 1251,1257 ----
        COMPARE_SCALAR_FIELD(deferrable);
        COMPARE_SCALAR_FIELD(initdeferred);
        COMPARE_SCALAR_FIELD(concurrent);
+       COMPARE_SCALAR_FIELD(oldNode);
  
        return true;
  }
diff --git a/src/backend/nodes/outfunindex 681f5f8..0dfa5f4 100644
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 1982,1987 **** _outIndexStmt(StringInfo str, IndexStmt *node)
--- 1982,1988 ----
        WRITE_BOOL_FIELD(deferrable);
        WRITE_BOOL_FIELD(initdeferred);
        WRITE_BOOL_FIELD(concurrent);
+       WRITE_OID_FIELD(oldNode);
  }
  
  static void
diff --git a/src/include/commands/dindex bbc024f..cbb5f1f 100644
*** a/src/include/commands/defrem.h
--- b/src/include/commands/defrem.h
***************
*** 18,24 ****
  
  
  /* commands/indexcmds.c */
! extern void DefineIndex(RangeVar *heapRelation,
                        char *indexRelationName,
                        Oid indexRelationId,
                        char *accessMethodName,
--- 18,24 ----
  
  
  /* commands/indexcmds.c */
! extern Oid DefineIndex(RangeVar *heapRelation,
                        char *indexRelationName,
                        Oid indexRelationId,
                        char *accessMethodName,
***************
*** 49,54 **** extern char *ChooseIndexName(const char *tabname, Oid 
namespaceId,
--- 49,59 ----
                                List *colnames, List *exclusionOpNames,
                                bool primary, bool isconstraint);
  extern List *ChooseIndexColumnNames(List *indexElems);
+ extern bool CheckIndexCompatible(Oid oldId,
+                                        RangeVar *heapRelation,
+                                        char *accessMethodName,
+                                        List *attributeList,
+                                        List *exclusionOpNames);
  extern Oid    GetDefaultOpClass(Oid type_id, Oid am_id);
  
  /* commands/functioncmds.c */
diff --git a/src/include/nodes/parseindex 14937d4..2d226a9 100644
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 2065,2070 **** typedef struct IndexStmt
--- 2065,2071 ----
        bool            deferrable;             /* is the constraint 
DEFERRABLE? */
        bool            initdeferred;   /* is the constraint INITIALLY 
DEFERRED? */
        bool            concurrent;             /* should this be a concurrent 
index build? */
+       Oid                     oldNode;                /* relfilenode of my 
former self */
  } IndexStmt;
  
  /* ----------------------
diff --git a/src/test/regress/GNUmakeindex 90aea6c..949f82b 100644
diff --git a/src/test/regress/expecnew file mode 100644
index 0000000..10dbcc7
diff --git a/src/test/regress/parallel_schedule b/srindex 376f28d..e3b515e 
100644
diff --git a/src/test/regress/serial_scheindex bb654f9..a7e6dc6 100644
diff --git a/src/test/regress/sql/big_anew file mode 100644
index 0000000..6ae9007
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to