</listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><symbol>DEPENDENCY_INTERNAL_AUTO</symbol> (<literal>I</literal>)</term>
+     <listitem>
+      <para>
+       The dependent object was created as part of creation of the
+       referenced object, and is really just a part of its internal
+       implementation.  A <command>DROP</command> of the dependent object
+       will be disallowed outright (we'll tell the user to issue a
+       <command>DROP</command> against the referenced object, instead).
+       While a regular internal dependency will prevent
+       the dependent object from being dropped while any such dependencies
+       remain, <literal>DEPENDENCY_INTERNAL_AUTO</literal> will allow such
+       a drop as long as the object can be found by following any of such
+       dependencies.
+       Example: an index on a partition is made internal-auto-dependent on
+       both the partition itself as well as on the index on the parent
+       partitioned table; so the partition index is dropped together with
+       either the partition it indexes, or with the parent index it is
+       attached to.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><symbol>DEPENDENCY_EXTENSION</symbol> (<literal>e</literal>)</term>
      <listitem>
 
 <synopsis>
 ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> RENAME TO <replaceable class="parameter">new_name</replaceable>
 ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> SET TABLESPACE <replaceable class="parameter">tablespace_name</replaceable>
+ALTER INDEX <replaceable class="parameter">name</replaceable> ATTACH PARTITION <replaceable class="parameter">index_name</replaceable>
 ALTER INDEX <replaceable class="parameter">name</replaceable> DEPENDS ON EXTENSION <replaceable class="parameter">extension_name</replaceable>
 ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">storage_parameter</replaceable> = <replaceable class="parameter">value</replaceable> [, ... ] )
 ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> RESET ( <replaceable class="parameter">storage_parameter</replaceable> [, ... ] )
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>ATTACH PARTITION</literal></term>
+    <listitem>
+     <para>
+      Causes the named index to become attached to the altered index.
+      The named index must be on a partition of the table containing the
+      index being altered, and have an equivalent definition.  An attached
+      index cannot be dropped by itself, and will automatically be dropped
+      if its parent index is dropped.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>DEPENDS ON EXTENSION</literal></term>
     <listitem>
 
       as a partition of the target table. The table can be attached
       as a partition for specific values using <literal>FOR VALUES
       </literal> or as a default partition by using <literal>DEFAULT
-      </literal>.
+      </literal>.  For each index in the target table, a corresponding
+      one will be created in the attached table; or, if an equivalent
+      index already exists, will be attached to the target table's index,
+      as if <command>ALTER INDEX ATTACH PARTITION</command> had been executed.
      </para>
 
      <para>
      <para>
       This form detaches specified partition of the target table.  The detached
       partition continues to exist as a standalone table, but no longer has any
-      ties to the table from which it was detached.
+      ties to the table from which it was detached.  Any indexes that were
+      attached to the target table's indexes are detached.
      </para>
     </listitem>
    </varlistentry>
 
 
  <refsynopsisdiv>
 <synopsis>
-CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> ] ON <replaceable class="parameter">table_name</replaceable> [ USING <replaceable class="parameter">method</replaceable> ]
+CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> ] ON [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ USING <replaceable class="parameter">method</replaceable> ]
     ( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] [, ...] )
     [ WITH ( <replaceable class="parameter">storage_parameter</replaceable> = <replaceable class="parameter">value</replaceable> [, ... ] ) ]
     [ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><literal>ONLY</literal></term>
+      <listitem>
+       <para>
+        Indicates not to recurse creating indexes on partitions, if the
+        table is partitioned.  The default is to recurse.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><replaceable class="parameter">table_name</replaceable></term>
       <listitem>
    linkend="xindex"/>.
   </para>
 
+  <para>
+   When <literal>CREATE INDEX</literal> is invoked on a partitioned
+   table, the default behavior is to recurse to all partitions to ensure
+   they all have matching indexes.
+   Each partition is first checked to determine whether an equivalent
+   index already exists, and if so, that index will become attached as a
+   partition index to the index being created, which will become its
+   parent index.
+   If no matching index exists, a new index will be created and
+   automatically attached; the name of the new index in each partition
+   will be determined as if no index name had been specified in the
+   command.
+   If the <literal>ONLY</literal> option is specified, no recursion
+   is done, and the index is marked invalid
+   (<command>ALTER INDEX ... ATTACH PARTITION</command> turns the index
+   valid, once all partitions acquire the index.)  Note, however, that
+   any partition that is created in the future using
+   <command>CREATE TABLE ... PARTITION OF</command> will automatically
+   contain the index regardless of whether this option was specified.
+  </para>
+
   <para>
    For index methods that support ordered scans (currently, only B-tree),
    the optional clauses <literal>ASC</literal>, <literal>DESC</literal>, <literal>NULLS
 
    reindex anything.
   </para>
 
+  <para>
+   Reindexing partitioned tables or partitioned indexes is not supported.
+   Each individual partition can be reindexed separately instead.
+  </para>
+
  </refsect1>
 
  <refsect1>
 
            options = view_reloptions(datum, false);
            break;
        case RELKIND_INDEX:
+       case RELKIND_PARTITIONED_INDEX:
            options = index_reloptions(amoptions, datum, false);
            break;
        case RELKIND_FOREIGN_TABLE:
 
 
    r = relation_open(relationId, lockmode);
 
-   if (r->rd_rel->relkind == RELKIND_INDEX)
+   if (r->rd_rel->relkind == RELKIND_INDEX ||
+       r->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                 errmsg("\"%s\" is an index",
 
    r = relation_openrv(relation, lockmode);
 
-   if (r->rd_rel->relkind == RELKIND_INDEX)
+   if (r->rd_rel->relkind == RELKIND_INDEX ||
+       r->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                 errmsg("\"%s\" is an index",
 
    if (r)
    {
-       if (r->rd_rel->relkind == RELKIND_INDEX)
+       if (r->rd_rel->relkind == RELKIND_INDEX ||
+           r->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
            ereport(ERROR,
                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                     errmsg("\"%s\" is an index",
 
 
    r = relation_open(relationId, lockmode);
 
-   if (r->rd_rel->relkind != RELKIND_INDEX)
+   if (r->rd_rel->relkind != RELKIND_INDEX &&
+       r->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                 errmsg("\"%s\" is not an index",
 
                    DefineIndex(relationId,
                                stmt,
                                $4,
+                               InvalidOid,
                                false,
                                false,
                                false,
                    DefineIndex(relationId,
                                stmt,
                                $5,
+                               InvalidOid,
                                false,
                                false,
                                false,
 
        pg_class_tuple = (Form_pg_class) GETSTRUCT(tuple);
 
        /* Not sensible to grant on an index */
-       if (pg_class_tuple->relkind == RELKIND_INDEX)
+       if (pg_class_tuple->relkind == RELKIND_INDEX ||
+           pg_class_tuple->relkind == RELKIND_PARTITIONED_INDEX)
            ereport(ERROR,
                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                     errmsg("\"%s\" is an index",
        pg_class_tuple = (Form_pg_class) GETSTRUCT(tuple);
 
        /* Indexes don't have permissions */
-       if (pg_class_tuple->relkind == RELKIND_INDEX)
+       if (pg_class_tuple->relkind == RELKIND_INDEX ||
+           pg_class_tuple->relkind == RELKIND_PARTITIONED_INDEX)
            return;
 
        /* Composite types don't have permissions either */
        pg_class_tuple = (Form_pg_class) GETSTRUCT(tuple);
 
        /* Indexes don't have permissions */
-       if (pg_class_tuple->relkind == RELKIND_INDEX)
+       if (pg_class_tuple->relkind == RELKIND_INDEX ||
+           pg_class_tuple->relkind == RELKIND_PARTITIONED_INDEX)
            return;
 
        /* Composite types don't have permissions either */
 
                /* FALL THRU */
 
            case DEPENDENCY_INTERNAL:
+           case DEPENDENCY_INTERNAL_AUTO:
 
                /*
                 * This object is part of the internal implementation of
                 * transform this deletion request into a delete of this
                 * owning object.
                 *
+                * For INTERNAL_AUTO dependencies, we don't enforce this;
+                * in other words, we don't follow the links back to the
+                * owning object.
+                */
+               if (foundDep->deptype == DEPENDENCY_INTERNAL_AUTO)
+                   break;
+
+               /*
                 * First, release caller's lock on this object and get
                 * deletion lock on the owning object.  (We must release
                 * caller's lock to avoid deadlock against a concurrent
                /* And we're done here. */
                systable_endscan(scan);
                return;
+
            case DEPENDENCY_PIN:
 
                /*
            case DEPENDENCY_AUTO_EXTENSION:
                subflags = DEPFLAG_AUTO;
                break;
+           case DEPENDENCY_INTERNAL_AUTO:
            case DEPENDENCY_INTERNAL:
                subflags = DEPFLAG_INTERNAL;
                break;
            {
                char        relKind = get_rel_relkind(object->objectId);
 
-               if (relKind == RELKIND_INDEX)
+               if (relKind == RELKIND_INDEX ||
+                   relKind == RELKIND_PARTITIONED_INDEX)
                {
                    bool        concurrent = ((flags & PERFORM_DELETION_CONCURRENTLY) != 0);
 
 
        case RELKIND_COMPOSITE_TYPE:
        case RELKIND_FOREIGN_TABLE:
        case RELKIND_PARTITIONED_TABLE:
+       case RELKIND_PARTITIONED_INDEX:
            create_storage = false;
 
            /*
 
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_constraint_fn.h"
+#include "catalog/pg_depend.h"
+#include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_tablespace.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "parser/parser.h"
+#include "rewrite/rewriteManip.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
                        int numatts, Oid indexoid);
 static void AppendAttributeTuples(Relation indexRelation, int numatts);
 static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
+                   Oid parentIndexId,
                    IndexInfo *indexInfo,
                    Oid *collationOids,
                    Oid *classOids,
                    bool primary,
                    bool isexclusion,
                    bool immediate,
-                   bool isvalid);
+                   bool isvalid,
+                   bool isready);
 static void index_update_stats(Relation rel,
                   bool hasindex, bool isprimary,
                   double reltuples);
 static void
 UpdateIndexRelation(Oid indexoid,
                    Oid heapoid,
+                   Oid parentIndexOid,
                    IndexInfo *indexInfo,
                    Oid *collationOids,
                    Oid *classOids,
                    bool primary,
                    bool isexclusion,
                    bool immediate,
-                   bool isvalid)
+                   bool isvalid,
+                   bool isready)
 {
    int2vector *indkey;
    oidvector  *indcollation;
    values[Anum_pg_index_indisclustered - 1] = BoolGetDatum(false);
    values[Anum_pg_index_indisvalid - 1] = BoolGetDatum(isvalid);
    values[Anum_pg_index_indcheckxmin - 1] = BoolGetDatum(false);
-   /* we set isvalid and isready the same way */
-   values[Anum_pg_index_indisready - 1] = BoolGetDatum(isvalid);
+   values[Anum_pg_index_indisready - 1] = BoolGetDatum(isready);
    values[Anum_pg_index_indislive - 1] = BoolGetDatum(true);
    values[Anum_pg_index_indisreplident - 1] = BoolGetDatum(false);
    values[Anum_pg_index_indkey - 1] = PointerGetDatum(indkey);
  * indexRelationId: normally, pass InvalidOid to let this routine
  *     generate an OID for the index.  During bootstrap this may be
  *     nonzero to specify a preselected OID.
+ * parentIndexRelid: if creating an index partition, the OID of the
+ *     parent index; otherwise InvalidOid.
  * relFileNode: normally, pass InvalidOid to get new storage.  May be
  *     nonzero to attach an existing valid build.
  * indexInfo: same info executor uses to insert into the index
  *     INDEX_CREATE_IF_NOT_EXISTS:
  *         do not throw an error if a relation with the same name
  *         already exists.
+ *     INDEX_CREATE_PARTITIONED:
+ *         create a partitioned index (table must be partitioned)
  * constr_flags: flags passed to index_constraint_create
  *     (only if INDEX_CREATE_ADD_CONSTRAINT is set)
  * allow_system_table_mods: allow table to be a system catalog
 index_create(Relation heapRelation,
             const char *indexRelationName,
             Oid indexRelationId,
+            Oid parentIndexRelid,
             Oid relFileNode,
             IndexInfo *indexInfo,
             List *indexColNames,
    int         i;
    char        relpersistence;
    bool        isprimary = (flags & INDEX_CREATE_IS_PRIMARY) != 0;
+   bool        invalid = (flags & INDEX_CREATE_INVALID) != 0;
    bool        concurrent = (flags & INDEX_CREATE_CONCURRENT) != 0;
+   bool        partitioned = (flags & INDEX_CREATE_PARTITIONED) != 0;
+   char        relkind;
 
    /* constraint flags can only be set when a constraint is requested */
    Assert((constr_flags == 0) ||
           ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0));
+   /* partitioned indexes must never be "built" by themselves */
+   Assert(!partitioned || (flags & INDEX_CREATE_SKIP_BUILD));
 
+   relkind = partitioned ? RELKIND_PARTITIONED_INDEX : RELKIND_INDEX;
    is_exclusion = (indexInfo->ii_ExclusionOps != NULL);
 
    pg_class = heap_open(RelationRelationId, RowExclusiveLock);
    }
 
    /*
-    * create the index relation's relcache entry and physical disk file. (If
-    * we fail further down, it's the smgr's responsibility to remove the disk
-    * file again.)
+    * create the index relation's relcache entry and, if necessary, the
+    * physical disk file. (If we fail further down, it's the smgr's
+    * responsibility to remove the disk file again, if any.)
     */
    indexRelation = heap_create(indexRelationName,
                                namespaceId,
                                indexRelationId,
                                relFileNode,
                                indexTupDesc,
-                               RELKIND_INDEX,
+                               relkind,
                                relpersistence,
                                shared_relation,
                                mapped_relation,
     *    (Or, could define a rule to maintain the predicate) --Nels, Feb '92
     * ----------------
     */
-   UpdateIndexRelation(indexRelationId, heapRelationId, indexInfo,
+   UpdateIndexRelation(indexRelationId, heapRelationId, parentIndexRelid,
+                       indexInfo,
                        collationObjectId, classObjectId, coloptions,
                        isprimary, is_exclusion,
                        (constr_flags & INDEX_CONSTR_CREATE_DEFERRABLE) == 0,
+                       !concurrent && !invalid,
                        !concurrent);
 
+   /* update pg_inherits, if needed */
+   if (OidIsValid(parentIndexRelid))
+       StoreSingleInheritance(indexRelationId, parentIndexRelid, 1);
+
    /*
     * Register constraint and dependencies for the index.
     *
        else
        {
            bool        have_simple_col = false;
+           DependencyType  deptype;
+
+           deptype = OidIsValid(parentIndexRelid) ? DEPENDENCY_INTERNAL_AUTO : DEPENDENCY_AUTO;
 
            /* Create auto dependencies on simply-referenced columns */
            for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
                    referenced.objectId = heapRelationId;
                    referenced.objectSubId = indexInfo->ii_KeyAttrNumbers[i];
 
-                   recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+                   recordDependencyOn(&myself, &referenced, deptype);
 
                    have_simple_col = true;
                }
                referenced.objectId = heapRelationId;
                referenced.objectSubId = 0;
 
-               recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+               recordDependencyOn(&myself, &referenced, deptype);
            }
        }
 
+       /* Store dependency on parent index, if any */
+       if (OidIsValid(parentIndexRelid))
+       {
+           referenced.classId = RelationRelationId;
+           referenced.objectId = parentIndexRelid;
+           referenced.objectSubId = 0;
+
+           recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL_AUTO);
+       }
+
        /* Store dependency on collations */
        /* The default collation is pinned, so don't bother recording it */
        for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
    }
 
    /*
-    * Schedule physical removal of the files
+    * Schedule physical removal of the files (if any)
     */
-   RelationDropStorage(userIndexRelation);
+   if (userIndexRelation->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
+       RelationDropStorage(userIndexRelation);
 
    /*
     * Close and flush the index's relcache entry, to ensure relcache doesn't
     */
    DeleteRelationTuple(indexId);
 
+   /*
+    * fix INHERITS relation
+    */
+   DeleteInheritsTuple(indexId, InvalidOid);
+
    /*
     * We are presently too lazy to attempt to compute the new correct value
     * of relhasindex (the next VACUUM will fix it if necessary). So there is
    ii->ii_BrokenHotChain = false;
 
    /* set up for possible use by index AM */
+   ii->ii_Am = index->rd_rel->relam;
    ii->ii_AmCache = NULL;
    ii->ii_Context = CurrentMemoryContext;
 
    return ii;
 }
 
+/*
+ * CompareIndexInfo
+ *     Return whether the properties of two indexes (in different tables)
+ *     indicate that they have the "same" definitions.
+ *
+ * Note: passing collations and opfamilies separately is a kludge.  Adding
+ * them to IndexInfo may result in better coding here and elsewhere.
+ *
+ * Use convert_tuples_by_name_map(index2, index1) to build the attmap.
+ */
+bool
+CompareIndexInfo(IndexInfo *info1, IndexInfo *info2,
+                Oid *collations1, Oid *collations2,
+                Oid *opfamilies1, Oid *opfamilies2,
+                AttrNumber *attmap, int maplen)
+{
+   int     i;
+
+   if (info1->ii_Unique != info2->ii_Unique)
+       return false;
+
+   /* indexes are only equivalent if they have the same access method */
+   if (info1->ii_Am != info2->ii_Am)
+       return false;
+
+   /* and same number of attributes */
+   if (info1->ii_NumIndexAttrs != info2->ii_NumIndexAttrs)
+       return false;
+
+   /*
+    * and columns match through the attribute map (actual attribute numbers
+    * might differ!)  Note that this implies that index columns that are
+    * expressions appear in the same positions.  We will next compare the
+    * expressions themselves.
+    */
+   for (i = 0; i < info1->ii_NumIndexAttrs; i++)
+   {
+       if (maplen < info2->ii_KeyAttrNumbers[i])
+           elog(ERROR, "incorrect attribute map");
+
+       if (attmap[info2->ii_KeyAttrNumbers[i] - 1] !=
+           info1->ii_KeyAttrNumbers[i])
+           return false;
+
+       if (collations1[i] != collations2[i])
+           return false;
+       if (opfamilies1[i] != opfamilies2[i])
+           return false;
+   }
+
+   /*
+    * For expression indexes: either both are expression indexes, or neither
+    * is; if they are, make sure the expressions match.
+    */
+   if ((info1->ii_Expressions != NIL) != (info2->ii_Expressions != NIL))
+       return false;
+   if (info1->ii_Expressions != NIL)
+   {
+       bool    found_whole_row;
+       Node   *mapped;
+
+       mapped = map_variable_attnos((Node *) info2->ii_Expressions,
+                                    1, 0, attmap, maplen,
+                                    InvalidOid, &found_whole_row);
+       if (found_whole_row)
+       {
+           /*
+            * we could throw an error here, but seems out of scope for this
+            * routine.
+            */
+           return false;
+       }
+
+       if (!equal(info1->ii_Expressions, mapped))
+           return false;
+   }
+
+   /* Partial index predicates must be identical, if they exist */
+   if ((info1->ii_Predicate == NULL) != (info2->ii_Predicate == NULL))
+       return false;
+   if (info1->ii_Predicate != NULL)
+   {
+       bool    found_whole_row;
+       Node   *mapped;
+
+       mapped = map_variable_attnos((Node *) info2->ii_Predicate,
+                                    1, 0, attmap, maplen,
+                                    InvalidOid, &found_whole_row);
+       if (found_whole_row)
+       {
+           /*
+            * we could throw an error here, but seems out of scope for this
+            * routine.
+            */
+           return false;
+       }
+       if (!equal(info1->ii_Predicate, mapped))
+           return false;
+   }
+
+   /* No support currently for comparing exclusion indexes. */
+   if (info1->ii_ExclusionOps != NULL || info2->ii_ExclusionOps != NULL)
+       return false;
+
+   return true;
+}
+
 /* ----------------
  *     BuildSpeculativeIndexInfo
  *         Add extra state to IndexInfo record
        elog(ERROR, "could not find tuple for relation %u", relid);
    rd_rel = (Form_pg_class) GETSTRUCT(tuple);
 
+   /* Should this be a more comprehensive test? */
+   Assert(rd_rel->relkind != RELKIND_PARTITIONED_INDEX);
+
    /* Apply required updates, if any, to copied tuple */
 
    dirty = false;
     */
    iRel = index_open(indexId, AccessExclusiveLock);
 
+   /*
+    * The case of reindexing partitioned tables and indexes is handled
+    * differently by upper layers, so this case shouldn't arise.
+    */
+   if (iRel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
+       elog(ERROR, "unsupported relation kind for index \"%s\"",
+            RelationGetRelationName(iRel));
+
    /*
     * Don't allow reindex on temp tables of other backends ... their local
     * buffer manager is not going to cope.
     */
    rel = heap_open(relid, ShareLock);
 
+   /*
+    * This may be useful when implemented someday; but that day is not today.
+    * For now, avoid erroring out when called in a multi-table context
+    * (REINDEX SCHEMA) and happen to come across a partitioned table.  The
+    * partitions may be reindexed on their own anyway.
+    */
+   if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+   {
+       ereport(WARNING,
+               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                errmsg("REINDEX of partitioned tables is not yet implemented, skipping \"%s\"",
+                       RelationGetRelationName(rel))));
+       heap_close(rel, ShareLock);
+       return false;
+   }
+
    toast_relid = rel->rd_rel->reltoastrelid;
 
    /*
 
    switch (objtype)
    {
        case OBJECT_INDEX:
-           if (relation->rd_rel->relkind != RELKIND_INDEX)
+           if (relation->rd_rel->relkind != RELKIND_INDEX &&
+               relation->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
                ereport(ERROR,
                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                         errmsg("\"%s\" is not an index",
                             relname);
            break;
        case RELKIND_INDEX:
+       case RELKIND_PARTITIONED_INDEX:
            appendStringInfo(buffer, _("index %s"),
                             relname);
            break;
            appendStringInfoString(buffer, "table");
            break;
        case RELKIND_INDEX:
+       case RELKIND_PARTITIONED_INDEX:
            appendStringInfoString(buffer, "index");
            break;
        case RELKIND_SEQUENCE:
 
 
        /*
         * We assume any internal dependency of an index on the constraint
-        * must be what we are looking for.  (The relkind test is just
-        * paranoia; there shouldn't be any such dependencies otherwise.)
+        * must be what we are looking for.
         */
        if (deprec->classid == RelationRelationId &&
            deprec->objsubid == 0 &&
-           deprec->deptype == DEPENDENCY_INTERNAL &&
-           get_rel_relkind(deprec->objid) == RELKIND_INDEX)
+           deprec->deptype == DEPENDENCY_INTERNAL)
        {
+           char        relkind = get_rel_relkind(deprec->objid);
+
+           /* This is pure paranoia; there shouldn't be any such */
+           if (relkind != RELKIND_INDEX &&
+               relkind != RELKIND_PARTITIONED_INDEX)
+               break;
+
            indexId = deprec->objid;
            break;
        }
 
 
    return result;
 }
+
+/*
+ * Create a single pg_inherits row with the given data
+ */
+void
+StoreSingleInheritance(Oid relationId, Oid parentOid, int32 seqNumber)
+{
+   Datum       values[Natts_pg_inherits];
+   bool        nulls[Natts_pg_inherits];
+   HeapTuple   tuple;
+   Relation    inhRelation;
+
+   inhRelation = heap_open(InheritsRelationId, RowExclusiveLock);
+
+   /*
+    * Make the pg_inherits entry
+    */
+   values[Anum_pg_inherits_inhrelid - 1] = ObjectIdGetDatum(relationId);
+   values[Anum_pg_inherits_inhparent - 1] = ObjectIdGetDatum(parentOid);
+   values[Anum_pg_inherits_inhseqno - 1] = Int32GetDatum(seqNumber);
+
+   memset(nulls, 0, sizeof(nulls));
+
+   tuple = heap_form_tuple(RelationGetDescr(inhRelation), values, nulls);
+
+   CatalogTupleInsert(inhRelation, tuple);
+
+   heap_freetuple(tuple);
+
+   heap_close(inhRelation, RowExclusiveLock);
+}
+
+/*
+ * DeleteInheritsTuple
+ *
+ * Delete pg_inherits tuples with the given inhrelid.  inhparent may be given
+ * as InvalidOid, in which case all tuples matching inhrelid are deleted;
+ * otherwise only delete tuples with the specified inhparent.
+ *
+ * Returns whether at least one row was deleted.
+ */
+bool
+DeleteInheritsTuple(Oid inhrelid, Oid inhparent)
+{
+   bool    found = false;
+   Relation    catalogRelation;
+   ScanKeyData key;
+   SysScanDesc scan;
+   HeapTuple   inheritsTuple;
+
+   /*
+    * Find pg_inherits entries by inhrelid.
+    */
+   catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
+   ScanKeyInit(&key,
+               Anum_pg_inherits_inhrelid,
+               BTEqualStrategyNumber, F_OIDEQ,
+               ObjectIdGetDatum(inhrelid));
+   scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
+                             true, NULL, 1, &key);
+
+   while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
+   {
+       Oid         parent;
+
+       /* Compare inhparent if it was given, and do the actual deletion. */
+       parent = ((Form_pg_inherits) GETSTRUCT(inheritsTuple))->inhparent;
+       if (!OidIsValid(inhparent) || parent == inhparent)
+       {
+           CatalogTupleDelete(catalogRelation, &inheritsTuple->t_self);
+           found = true;
+       }
+   }
+
+   /* Done */
+   systable_endscan(scan);
+   heap_close(catalogRelation, RowExclusiveLock);
+
+   return found;
+}
 
    indexInfo->ii_ReadyForInserts = true;
    indexInfo->ii_Concurrent = false;
    indexInfo->ii_BrokenHotChain = false;
+   indexInfo->ii_Am = BTREE_AM_OID;
    indexInfo->ii_AmCache = NULL;
    indexInfo->ii_Context = CurrentMemoryContext;
 
    coloptions[1] = 0;
 
    index_create(toast_rel, toast_idxname, toastIndexOid, InvalidOid,
+                InvalidOid,
                 indexInfo,
                 list_make2("chunk_id", "chunk_seq"),
                 BTREE_AM_OID,
 
 #include "catalog/catalog.h"
 #include "catalog/index.h"
 #include "catalog/indexing.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_tablespace.h"
 #include "commands/tablespace.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/planner.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
+#include "rewrite/rewriteManip.h"
 #include "storage/lmgr.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
 static List *ChooseIndexColumnNames(List *indexElems);
 static void RangeVarCallbackForReindexIndex(const RangeVar *relation,
                                Oid relId, Oid oldRelId, void *arg);
+static void ReindexPartitionedIndex(Relation parentIdx);
 
 /*
  * CheckIndexCompatible
    indexInfo->ii_ExclusionOps = NULL;
    indexInfo->ii_ExclusionProcs = NULL;
    indexInfo->ii_ExclusionStrats = NULL;
+   indexInfo->ii_Am = accessMethodId;
    indexInfo->ii_AmCache = NULL;
    indexInfo->ii_Context = CurrentMemoryContext;
    typeObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
  * 'stmt': IndexStmt describing the properties of the new index.
  * 'indexRelationId': normally InvalidOid, but during bootstrap can be
  *     nonzero to specify a preselected OID for the index.
+ * 'parentIndexId': the OID of the parent index; InvalidOid if not the child
+ *     of a partitioned index.
  * 'is_alter_table': this is due to an ALTER rather than a CREATE operation.
  * 'check_rights': check for CREATE rights in namespace and tablespace.  (This
  *     should be true except when ALTER is deleting/recreating an index.)
  * 'check_not_in_use': check for table not already in use in current session.
  *     This should be true unless caller is holding the table open, in which
  *     case the caller had better have checked it earlier.
- * 'skip_build': make the catalog entries but leave the index file empty;
- *     it will be filled later.
+ * 'skip_build': make the catalog entries but don't create the index files
  * 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
  *
  * Returns the object address of the created index.
 DefineIndex(Oid relationId,
            IndexStmt *stmt,
            Oid indexRelationId,
+           Oid parentIndexId,
            bool is_alter_table,
            bool check_rights,
            bool check_not_in_use,
    IndexAmRoutine *amRoutine;
    bool        amcanorder;
    amoptions_function amoptions;
+   bool        partitioned;
    Datum       reloptions;
    int16      *coloptions;
    IndexInfo  *indexInfo;
    {
        case RELKIND_RELATION:
        case RELKIND_MATVIEW:
+       case RELKIND_PARTITIONED_TABLE:
            /* OK */
            break;
        case RELKIND_FOREIGN_TABLE:
+           /*
+            * Custom error message for FOREIGN TABLE since the term is close
+            * to a regular table and can confuse the user.
+            */
            ereport(ERROR,
                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                     errmsg("cannot create index on foreign table \"%s\"",
                            RelationGetRelationName(rel))));
-       case RELKIND_PARTITIONED_TABLE:
-           ereport(ERROR,
-                   (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                    errmsg("cannot create index on partitioned table \"%s\"",
-                           RelationGetRelationName(rel))));
        default:
            ereport(ERROR,
                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                     errmsg("\"%s\" is not a table or materialized view",
                            RelationGetRelationName(rel))));
+           break;
+   }
+
+   /*
+    * Establish behavior for partitioned tables, and verify sanity of
+    * parameters.
+    *
+    * We do not build an actual index in this case; we only create a few
+    * catalog entries.  The actual indexes are built by recursing for each
+    * partition.
+    */
+   partitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;
+   if (partitioned)
+   {
+       if (stmt->concurrent)
+           ereport(ERROR,
+                   (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                    errmsg("cannot create index on partitioned table \"%s\" concurrently",
+                           RelationGetRelationName(rel))));
+       if (stmt->unique)
+           ereport(ERROR,
+                   (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                    errmsg("cannot create unique index on partitioned table \"%s\"",
+                           RelationGetRelationName(rel))));
+       if (stmt->excludeOpNames)
+           ereport(ERROR,
+                   (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                    errmsg("cannot create exclusion constraints on partitioned table \"%s\"",
+                           RelationGetRelationName(rel))));
+       if (stmt->primary || stmt->isconstraint)
+           ereport(ERROR,
+                   (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                    errmsg("cannot create constraints on partitioned tables")));
    }
 
    /*
    indexInfo->ii_ReadyForInserts = !stmt->concurrent;
    indexInfo->ii_Concurrent = stmt->concurrent;
    indexInfo->ii_BrokenHotChain = false;
+   indexInfo->ii_Am = accessMethodId;
    indexInfo->ii_AmCache = NULL;
    indexInfo->ii_Context = CurrentMemoryContext;
 
    /*
     * Make the catalog entries for the index, including constraints. This
     * step also actually builds the index, except if caller requested not to
-    * or in concurrent mode, in which case it'll be done later.
+    * or in concurrent mode, in which case it'll be done later, or
+    * doing a partitioned index (because those don't have storage).
     */
    flags = constr_flags = 0;
    if (stmt->isconstraint)
        flags |= INDEX_CREATE_ADD_CONSTRAINT;
-   if (skip_build || stmt->concurrent)
+   if (skip_build || stmt->concurrent || partitioned)
        flags |= INDEX_CREATE_SKIP_BUILD;
    if (stmt->if_not_exists)
        flags |= INDEX_CREATE_IF_NOT_EXISTS;
    if (stmt->concurrent)
        flags |= INDEX_CREATE_CONCURRENT;
+   if (partitioned)
+       flags |= INDEX_CREATE_PARTITIONED;
    if (stmt->primary)
        flags |= INDEX_CREATE_IS_PRIMARY;
+   if (partitioned && stmt->relation && !stmt->relation->inh)
+       flags |= INDEX_CREATE_INVALID;
 
    if (stmt->deferrable)
        constr_flags |= INDEX_CONSTR_CREATE_DEFERRABLE;
        constr_flags |= INDEX_CONSTR_CREATE_INIT_DEFERRED;
 
    indexRelationId =
-       index_create(rel, indexRelationName, indexRelationId, stmt->oldNode,
-                    indexInfo, indexColNames,
+       index_create(rel, indexRelationName, indexRelationId, parentIndexId,
+                    stmt->oldNode, indexInfo, indexColNames,
                     accessMethodId, tablespaceId,
                     collationObjectId, classObjectId,
                     coloptions, reloptions,
        CreateComments(indexRelationId, RelationRelationId, 0,
                       stmt->idxcomment);
 
+   if (partitioned)
+   {
+       /*
+        * Unless caller specified to skip this step (via ONLY), process
+        * each partition to make sure they all contain a corresponding index.
+        *
+        * If we're called internally (no stmt->relation), recurse always.
+        */
+       if (!stmt->relation || stmt->relation->inh)
+       {
+           PartitionDesc partdesc = RelationGetPartitionDesc(rel);
+           int         nparts = partdesc->nparts;
+           Oid        *part_oids = palloc(sizeof(Oid) * nparts);
+           bool        invalidate_parent = false;
+           TupleDesc   parentDesc;
+           Oid        *opfamOids;
+
+           memcpy(part_oids, partdesc->oids, sizeof(Oid) * nparts);
+
+           parentDesc = CreateTupleDescCopy(RelationGetDescr(rel));
+           opfamOids = palloc(sizeof(Oid) * numberOfAttributes);
+           for (i = 0; i < numberOfAttributes; i++)
+               opfamOids[i] = get_opclass_family(classObjectId[i]);
+
+           heap_close(rel, NoLock);
+
+           /*
+            * For each partition, scan all existing indexes; if one matches
+            * our index definition and is not already attached to some other
+            * parent index, attach it to the one we just created.
+            *
+            * If none matches, build a new index by calling ourselves
+            * recursively with the same options (except for the index name).
+            */
+           for (i = 0; i < nparts; i++)
+           {
+               Oid     childRelid = part_oids[i];
+               Relation childrel;
+               List   *childidxs;
+               ListCell *cell;
+               AttrNumber *attmap;
+               bool    found = false;
+               int     maplen;
+
+               childrel = heap_open(childRelid, lockmode);
+               childidxs = RelationGetIndexList(childrel);
+               attmap =
+                   convert_tuples_by_name_map(RelationGetDescr(childrel),
+                                              parentDesc,
+                                              gettext_noop("could not convert row type"));
+               maplen = parentDesc->natts;
+
+
+               foreach(cell, childidxs)
+               {
+                   Oid         cldidxid = lfirst_oid(cell);
+                   Relation    cldidx;
+                   IndexInfo  *cldIdxInfo;
+
+                   /* this index is already partition of another one */
+                   if (has_superclass(cldidxid))
+                       continue;
+
+                   cldidx = index_open(cldidxid, lockmode);
+                   cldIdxInfo = BuildIndexInfo(cldidx);
+                   if (CompareIndexInfo(cldIdxInfo, indexInfo,
+                                        cldidx->rd_indcollation,
+                                        collationObjectId,
+                                        cldidx->rd_opfamily,
+                                        opfamOids,
+                                        attmap, maplen))
+                   {
+                       /*
+                        * Found a match.  Attach index to parent and we're
+                        * done, but keep lock till commit.
+                        */
+                       IndexSetParentIndex(cldidx, indexRelationId);
+
+                       if (!IndexIsValid(cldidx->rd_index))
+                           invalidate_parent = true;
+
+                       found = true;
+                       index_close(cldidx, NoLock);
+                       break;
+                   }
+
+                   index_close(cldidx, lockmode);
+               }
+
+               list_free(childidxs);
+               heap_close(childrel, NoLock);
+
+               /*
+                * If no matching index was found, create our own.
+                */
+               if (!found)
+               {
+                   IndexStmt  *childStmt = copyObject(stmt);
+                   bool        found_whole_row;
+
+                   childStmt->whereClause =
+                       map_variable_attnos(stmt->whereClause, 1, 0,
+                                           attmap, maplen,
+                                           InvalidOid, &found_whole_row);
+                   if (found_whole_row)
+                       elog(ERROR, "cannot convert whole-row table reference");
+
+                   childStmt->idxname = NULL;
+                   childStmt->relationId = childRelid;
+                   DefineIndex(childRelid, childStmt,
+                               InvalidOid,         /* no predefined OID */
+                               indexRelationId,    /* this is our child */
+                               false, check_rights, check_not_in_use,
+                               false, quiet);
+               }
+
+               pfree(attmap);
+           }
+
+           /*
+            * The pg_index row we inserted for this index was marked
+            * indisvalid=true.  But if we attached an existing index that
+            * is invalid, this is incorrect, so update our row to
+            * invalid too.
+            */
+           if (invalidate_parent)
+           {
+               Relation    pg_index = heap_open(IndexRelationId, RowExclusiveLock);
+               HeapTuple   tup,
+                           newtup;
+
+               tup = SearchSysCache1(INDEXRELID,
+                                     ObjectIdGetDatum(indexRelationId));
+               if (!tup)
+                   elog(ERROR, "cache lookup failed for index %u",
+                        indexRelationId);
+               newtup = heap_copytuple(tup);
+               ((Form_pg_index) GETSTRUCT(newtup))->indisvalid = false;
+               CatalogTupleUpdate(pg_index, &tup->t_self, newtup);
+               ReleaseSysCache(tup);
+               heap_close(pg_index, RowExclusiveLock);
+               heap_freetuple(newtup);
+           }
+       }
+       else
+           heap_close(rel, NoLock);
+
+       /*
+        * Indexes on partitioned tables are not themselves built, so we're
+        * done here.
+        */
+       return address;
+   }
+
    if (!stmt->concurrent)
    {
        /* Close the heap and we're done, in the non-concurrent case */
  * ReindexIndex
  *     Recreate a specific index.
  */
-Oid
+void
 ReindexIndex(RangeVar *indexRelation, int options)
 {
    Oid         indOid;
     * lock on the index.
     */
    irel = index_open(indOid, NoLock);
+
+   if (irel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
+   {
+       ReindexPartitionedIndex(irel);
+       return;
+   }
+
    persistence = irel->rd_rel->relpersistence;
    index_close(irel, NoLock);
 
    reindex_index(indOid, false, persistence, options);
-
-   return indOid;
 }
 
 /*
    relkind = get_rel_relkind(relId);
    if (!relkind)
        return;
-   if (relkind != RELKIND_INDEX)
+   if (relkind != RELKIND_INDEX &&
+       relkind != RELKIND_PARTITIONED_INDEX)
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                 errmsg("\"%s\" is not an index", relation->relname)));
        /*
         * Only regular tables and matviews can have indexes, so ignore any
         * other kind of relation.
+        *
+        * It is tempting to also consider partitioned tables here, but that
+        * has the problem that if the children are in the same schema, they
+        * would be processed twice.  Maybe we could have a separate list of
+        * partitioned tables, and expand that afterwards into relids,
+        * ignoring any duplicates.
         */
        if (classtuple->relkind != RELKIND_RELATION &&
            classtuple->relkind != RELKIND_MATVIEW)
 
    MemoryContextDelete(private_context);
 }
+
+/*
+ * ReindexPartitionedIndex
+ *     Reindex each child of the given partitioned index.
+ *
+ * Not yet implemented.
+ */
+static void
+ReindexPartitionedIndex(Relation parentIdx)
+{
+   ereport(ERROR,
+           (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+            errmsg("REINDEX is not yet implemented for partitioned indexes")));
+}
+
+/*
+ * Insert or delete an appropriate pg_inherits tuple to make the given index
+ * be a partition of the indicated parent index.
+ *
+ * This also corrects the pg_depend information for the affected index.
+ */
+void
+IndexSetParentIndex(Relation partitionIdx, Oid parentOid)
+{
+   Relation    pg_inherits;
+   ScanKeyData key[2];
+   SysScanDesc scan;
+   Oid         partRelid = RelationGetRelid(partitionIdx);
+   HeapTuple   tuple;
+   bool        fix_dependencies;
+
+   /* Make sure this is an index */
+   Assert(partitionIdx->rd_rel->relkind == RELKIND_INDEX ||
+          partitionIdx->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
+
+   /*
+    * Scan pg_inherits for rows linking our index to some parent.
+    */
+   pg_inherits = relation_open(InheritsRelationId, RowExclusiveLock);
+   ScanKeyInit(&key[0],
+               Anum_pg_inherits_inhrelid,
+               BTEqualStrategyNumber, F_OIDEQ,
+               ObjectIdGetDatum(partRelid));
+   ScanKeyInit(&key[1],
+               Anum_pg_inherits_inhseqno,
+               BTEqualStrategyNumber, F_INT4EQ,
+               Int32GetDatum(1));
+   scan = systable_beginscan(pg_inherits, InheritsRelidSeqnoIndexId, true,
+                             NULL, 2, key);
+   tuple = systable_getnext(scan);
+
+   if (!HeapTupleIsValid(tuple))
+   {
+       if (parentOid == InvalidOid)
+       {
+           /*
+            * No pg_inherits row, and no parent wanted: nothing to do in
+            * this case.
+            */
+           fix_dependencies = false;
+       }
+       else
+       {
+           Datum   values[Natts_pg_inherits];
+           bool    isnull[Natts_pg_inherits];
+
+           /*
+            * No pg_inherits row exists, and we want a parent for this index,
+            * so insert it.
+            */
+           values[Anum_pg_inherits_inhrelid - 1] = ObjectIdGetDatum(partRelid);
+           values[Anum_pg_inherits_inhparent - 1] =
+               ObjectIdGetDatum(parentOid);
+           values[Anum_pg_inherits_inhseqno - 1] = Int32GetDatum(1);
+           memset(isnull, false, sizeof(isnull));
+
+           tuple = heap_form_tuple(RelationGetDescr(pg_inherits),
+                                   values, isnull);
+           CatalogTupleInsert(pg_inherits, tuple);
+
+           fix_dependencies = true;
+       }
+   }
+   else
+   {
+       Form_pg_inherits    inhForm = (Form_pg_inherits) GETSTRUCT(tuple);
+
+       if (parentOid == InvalidOid)
+       {
+           /*
+            * There exists a pg_inherits row, which we want to clear; do so.
+            */
+           CatalogTupleDelete(pg_inherits, &tuple->t_self);
+           fix_dependencies = true;
+       }
+       else
+       {
+           /*
+            * A pg_inherits row exists.  If it's the same we want, then we're
+            * good; if it differs, that amounts to a corrupt catalog and
+            * should not happen.
+            */
+           if (inhForm->inhparent != parentOid)
+           {
+               /* unexpected: we should not get called in this case */
+               elog(ERROR, "bogus pg_inherit row: inhrelid %u inhparent %u",
+                    inhForm->inhrelid, inhForm->inhparent);
+           }
+
+           /* already in the right state */
+           fix_dependencies = false;
+       }
+   }
+
+   /* done with pg_inherits */
+   systable_endscan(scan);
+   relation_close(pg_inherits, RowExclusiveLock);
+
+   if (fix_dependencies)
+   {
+       ObjectAddress partIdx;
+
+       /*
+        * Insert/delete pg_depend rows.  If setting a parent, add an
+        * INTERNAL_AUTO dependency to the parent index; if making standalone,
+        * remove all existing rows and put back the regular dependency on the
+        * table.
+        */
+       ObjectAddressSet(partIdx, RelationRelationId, partRelid);
+
+       if (OidIsValid(parentOid))
+       {
+           ObjectAddress   parentIdx;
+
+           ObjectAddressSet(parentIdx, RelationRelationId, parentOid);
+           recordDependencyOn(&partIdx, &parentIdx, DEPENDENCY_INTERNAL_AUTO);
+       }
+       else
+       {
+           ObjectAddress   partitionTbl;
+
+           ObjectAddressSet(partitionTbl, RelationRelationId,
+                            partitionIdx->rd_index->indrelid);
+
+           deleteDependencyRecordsForClass(RelationRelationId, partRelid,
+                                           RelationRelationId,
+                                           DEPENDENCY_INTERNAL_AUTO);
+
+           recordDependencyOn(&partIdx, &partitionTbl, DEPENDENCY_AUTO);
+       }
+   }
+}
 
        gettext_noop("table \"%s\" does not exist, skipping"),
        gettext_noop("\"%s\" is not a table"),
    gettext_noop("Use DROP TABLE to remove a table.")},
+   {RELKIND_PARTITIONED_INDEX,
+       ERRCODE_UNDEFINED_OBJECT,
+       gettext_noop("index \"%s\" does not exist"),
+       gettext_noop("index \"%s\" does not exist, skipping"),
+       gettext_noop("\"%s\" is not an index"),
+   gettext_noop("Use DROP INDEX to remove an index.")},
    {'\0', 0, NULL, NULL, NULL, NULL}
 };
 
 #define        ATT_INDEX               0x0008
 #define        ATT_COMPOSITE_TYPE      0x0010
 #define        ATT_FOREIGN_TABLE       0x0020
+#define        ATT_PARTITIONED_INDEX   0x0040
 
 /*
  * Partition tables are expected to be dropped when the parent partitioned
 static void RemoveInheritance(Relation child_rel, Relation parent_rel);
 static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
                      PartitionCmd *cmd);
+static void AttachPartitionEnsureIndexes(Relation rel, Relation attachrel);
 static void ValidatePartitionConstraints(List **wqueue, Relation scanrel,
                             List *scanrel_children,
                             List *partConstraint,
                             bool validate_default);
 static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
+static ObjectAddress ATExecAttachPartitionIdx(List **wqueue, Relation rel,
+                        RangeVar *name);
+static void validatePartitionedIndex(Relation partedIdx, Relation partedTbl);
+static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
+                     Relation partitionTbl);
 
 
 /* ----------------------------------------------------------------
 
        StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
                          partopclass, partcollation);
+
+       /* make it all visible */
+       CommandCounterIncrement();
+   }
+
+   /*
+    * If we're creating a partition, create now all the indexes defined in
+    * the parent.  We can't do it earlier, because DefineIndex wants to know
+    * the partition key which we just stored.
+    */
+   if (stmt->partbound)
+   {
+       Oid         parentId = linitial_oid(inheritOids);
+       Relation    parent;
+       List       *idxlist;
+       ListCell   *cell;
+
+       /* Already have strong enough lock on the parent */
+       parent = heap_open(parentId, NoLock);
+       idxlist = RelationGetIndexList(parent);
+
+       /*
+        * For each index in the parent table, create one in the partition
+        */
+       foreach(cell, idxlist)
+       {
+           Relation    idxRel = index_open(lfirst_oid(cell), AccessShareLock);
+           AttrNumber *attmap;
+           IndexStmt  *idxstmt;
+
+           attmap = convert_tuples_by_name_map(RelationGetDescr(rel),
+                                               RelationGetDescr(parent),
+                                               gettext_noop("could not convert row type"));
+           idxstmt =
+               generateClonedIndexStmt(NULL, RelationGetRelid(rel), idxRel,
+                                       attmap, RelationGetDescr(rel)->natts);
+           DefineIndex(RelationGetRelid(rel),
+                       idxstmt,
+                       InvalidOid,
+                       RelationGetRelid(idxRel),
+                       false, false, false, false, false);
+
+           index_close(idxRel, AccessShareLock);
+       }
+
+       list_free(idxlist);
+       heap_close(parent, NoLock);
    }
 
    /*
     * but RemoveRelations() can only pass one relkind for a given relation.
     * It chooses RELKIND_RELATION for both regular and partitioned tables.
     * That means we must be careful before giving the wrong type error when
-    * the relation is RELKIND_PARTITIONED_TABLE.
+    * the relation is RELKIND_PARTITIONED_TABLE.  An equivalent problem
+    * exists with indexes.
     */
    if (classform->relkind == RELKIND_PARTITIONED_TABLE)
        expected_relkind = RELKIND_RELATION;
+   else if (classform->relkind == RELKIND_PARTITIONED_INDEX)
+       expected_relkind = RELKIND_INDEX;
    else
        expected_relkind = classform->relkind;
 
     * we do it the other way around.  No error if we don't find a pg_index
     * entry, though --- the relation may have been dropped.
     */
-   if (relkind == RELKIND_INDEX && relOid != oldRelOid)
+   if ((relkind == RELKIND_INDEX || relkind == RELKIND_PARTITIONED_INDEX) &&
+       relOid != oldRelOid)
    {
        state->heapOid = IndexGetRelation(relOid, true);
        if (OidIsValid(state->heapOid))
                         int32 seqNumber, Relation inhRelation,
                         bool child_is_partition)
 {
-   TupleDesc   desc = RelationGetDescr(inhRelation);
-   Datum       values[Natts_pg_inherits];
-   bool        nulls[Natts_pg_inherits];
    ObjectAddress childobject,
                parentobject;
-   HeapTuple   tuple;
-
-   /*
-    * Make the pg_inherits entry
-    */
-   values[Anum_pg_inherits_inhrelid - 1] = ObjectIdGetDatum(relationId);
-   values[Anum_pg_inherits_inhparent - 1] = ObjectIdGetDatum(parentOid);
-   values[Anum_pg_inherits_inhseqno - 1] = Int32GetDatum(seqNumber);
 
-   memset(nulls, 0, sizeof(nulls));
-
-   tuple = heap_form_tuple(desc, values, nulls);
-
-   CatalogTupleInsert(inhRelation, tuple);
-
-   heap_freetuple(tuple);
+   /* store the pg_inherits row */
+   StoreSingleInheritance(relationId, parentOid, seqNumber);
 
    /*
     * Store a dependency too
        relkind != RELKIND_MATVIEW &&
        relkind != RELKIND_COMPOSITE_TYPE &&
        relkind != RELKIND_INDEX &&
+       relkind != RELKIND_PARTITIONED_INDEX &&
        relkind != RELKIND_FOREIGN_TABLE &&
        relkind != RELKIND_PARTITIONED_TABLE)
        ereport(ERROR,
    /*
     * Also rename the associated constraint, if any.
     */
-   if (targetrelation->rd_rel->relkind == RELKIND_INDEX)
+   if (targetrelation->rd_rel->relkind == RELKIND_INDEX ||
+       targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
    {
        Oid         constraintId = get_index_constraint(myrelid);
 
                        stmt, RelationGetRelationName(rel))));
 
    if (rel->rd_rel->relkind != RELKIND_INDEX &&
+       rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
        AfterTriggerPendingOnRel(RelationGetRelid(rel)))
        ereport(ERROR,
                (errcode(ERRCODE_OBJECT_IN_USE),
            pass = AT_PASS_MISC;
            break;
        case AT_AttachPartition:
+           ATSimplePermissions(rel, ATT_TABLE | ATT_PARTITIONED_INDEX);
+           /* No command-specific prep needed */
+           pass = AT_PASS_MISC;
+           break;
        case AT_DetachPartition:
            ATSimplePermissions(rel, ATT_TABLE);
            /* No command-specific prep needed */
            ATExecGenericOptions(rel, (List *) cmd->def);
            break;
        case AT_AttachPartition:
-           ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);
+           if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+               ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);
+           else
+               ATExecAttachPartitionIdx(wqueue, rel,
+                                        ((PartitionCmd *) cmd->def)->name);
            break;
        case AT_DetachPartition:
+           /* ATPrepCmd ensures it must be a table */
+           Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
            ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
            break;
        default:                /* oops */
    {
        AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
 
-       /* Foreign tables have no storage, nor do partitioned tables. */
+       /*
+        * Foreign tables have no storage, nor do partitioned tables and
+        * indexes.
+        */
        if (tab->relkind == RELKIND_FOREIGN_TABLE ||
-           tab->relkind == RELKIND_PARTITIONED_TABLE)
+           tab->relkind == RELKIND_PARTITIONED_TABLE ||
+           tab->relkind == RELKIND_PARTITIONED_INDEX)
            continue;
 
        /*
        case RELKIND_INDEX:
            actual_target = ATT_INDEX;
            break;
+       case RELKIND_PARTITIONED_INDEX:
+           actual_target = ATT_PARTITIONED_INDEX;
+           break;
        case RELKIND_COMPOSITE_TYPE:
            actual_target = ATT_COMPOSITE_TYPE;
            break;
    if (rel->rd_rel->relkind != RELKIND_RELATION &&
        rel->rd_rel->relkind != RELKIND_MATVIEW &&
        rel->rd_rel->relkind != RELKIND_INDEX &&
+       rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
        rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
        rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
        ereport(ERROR,
     * We allow referencing columns by numbers only for indexes, since table
     * column numbers could contain gaps if columns are later dropped.
     */
-   if (rel->rd_rel->relkind != RELKIND_INDEX && !colName)
+   if (rel->rd_rel->relkind != RELKIND_INDEX &&
+       rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
+       !colName)
        ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                 errmsg("cannot refer to non-index column by number")));
                 errmsg("cannot alter system column \"%s\"",
                        colName)));
 
-   if (rel->rd_rel->relkind == RELKIND_INDEX &&
+   if ((rel->rd_rel->relkind == RELKIND_INDEX ||
+        rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) &&
        rel->rd_index->indkey.values[attnum - 1] != 0)
        ereport(ERROR,
                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    address = DefineIndex(RelationGetRelid(rel),
                          stmt,
                          InvalidOid,   /* no predefined OID */
+                         InvalidOid,   /* no parent index */
                          true, /* is_alter_table */
                          check_rights,
                          false,    /* check_not_in_use - we did it already */
                {
                    char        relKind = get_rel_relkind(foundObject.objectId);
 
-                   if (relKind == RELKIND_INDEX)
+                   if (relKind == RELKIND_INDEX ||
+                       relKind == RELKIND_PARTITIONED_INDEX)
                    {
                        Assert(foundObject.objectSubId == 0);
                        if (!list_member_oid(tab->changedIndexOids, foundObject.objectId))
                newOwnerId = tuple_class->relowner;
            }
            break;
+       case RELKIND_PARTITIONED_INDEX:
+           if (recursing)
+               break;
+           ereport(ERROR,
+                   (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                    errmsg("cannot change owner of index \"%s\"",
+                           NameStr(tuple_class->relname)),
+                    errhint("Change the ownership of the index's table, instead.")));
+           break;
        case RELKIND_SEQUENCE:
            if (!recursing &&
                tuple_class->relowner != newOwnerId)
         */
        if (tuple_class->relkind != RELKIND_COMPOSITE_TYPE &&
            tuple_class->relkind != RELKIND_INDEX &&
+           tuple_class->relkind != RELKIND_PARTITIONED_INDEX &&
            tuple_class->relkind != RELKIND_TOASTVALUE)
            changeDependencyOnOwner(RelationRelationId, relationOid,
                                    newOwnerId);
        /*
         * Also change the ownership of the table's row type, if it has one
         */
-       if (tuple_class->relkind != RELKIND_INDEX)
+       if (tuple_class->relkind != RELKIND_INDEX &&
+           tuple_class->relkind != RELKIND_PARTITIONED_INDEX)
            AlterTypeOwnerInternal(tuple_class->reltype, newOwnerId);
 
        /*
         * relation, as well as its toast table (if it has one).
         */
        if (tuple_class->relkind == RELKIND_RELATION ||
+           tuple_class->relkind == RELKIND_PARTITIONED_TABLE ||
            tuple_class->relkind == RELKIND_MATVIEW ||
            tuple_class->relkind == RELKIND_TOASTVALUE)
        {
            (void) view_reloptions(newOptions, true);
            break;
        case RELKIND_INDEX:
+       case RELKIND_PARTITIONED_INDEX:
            (void) index_reloptions(rel->rd_amroutine->amoptions, newOptions, true);
            break;
        default:
             relForm->relkind != RELKIND_RELATION &&
             relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
            (stmt->objtype == OBJECT_INDEX &&
-            relForm->relkind != RELKIND_INDEX) ||
+            relForm->relkind != RELKIND_INDEX &&
+            relForm->relkind != RELKIND_PARTITIONED_INDEX) ||
            (stmt->objtype == OBJECT_MATVIEW &&
             relForm->relkind != RELKIND_MATVIEW))
            continue;
    Relation    catalogRelation;
    SysScanDesc scan;
    ScanKeyData key[3];
-   HeapTuple   inheritsTuple,
-               attributeTuple,
+   HeapTuple   attributeTuple,
                constraintTuple;
    List       *connames;
-   bool        found = false;
+   bool        found;
    bool        child_is_partition = false;
 
    /* If parent_rel is a partitioned table, child_rel must be a partition */
    if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
        child_is_partition = true;
 
-   /*
-    * Find and destroy the pg_inherits entry linking the two, or error out if
-    * there is none.
-    */
-   catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
-   ScanKeyInit(&key[0],
-               Anum_pg_inherits_inhrelid,
-               BTEqualStrategyNumber, F_OIDEQ,
-               ObjectIdGetDatum(RelationGetRelid(child_rel)));
-   scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
-                             true, NULL, 1, key);
-
-   while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
-   {
-       Oid         inhparent;
-
-       inhparent = ((Form_pg_inherits) GETSTRUCT(inheritsTuple))->inhparent;
-       if (inhparent == RelationGetRelid(parent_rel))
-       {
-           CatalogTupleDelete(catalogRelation, &inheritsTuple->t_self);
-           found = true;
-           break;
-       }
-   }
-
-   systable_endscan(scan);
-   heap_close(catalogRelation, RowExclusiveLock);
-
+   found = DeleteInheritsTuple(RelationGetRelid(child_rel),
+                               RelationGetRelid(parent_rel));
    if (!found)
    {
        if (child_is_partition)
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                 errmsg("\"%s\" is not a composite type", rv->relname)));
 
-   if (reltype == OBJECT_INDEX && relkind != RELKIND_INDEX
+   if (reltype == OBJECT_INDEX && relkind != RELKIND_INDEX &&
+       relkind != RELKIND_PARTITIONED_INDEX
        && !IsA(stmt, RenameStmt))
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
    /* Update the pg_class entry. */
    StorePartitionBound(attachrel, rel, cmd->bound);
 
+   /* Ensure there exists a correct set of indexes in the partition. */
+   AttachPartitionEnsureIndexes(rel, attachrel);
+
    /*
     * Generate partition constraint from the partition bound specification.
     * If the parent itself is a partition, make sure to include its
    return address;
 }
 
+/*
+ * AttachPartitionEnsureIndexes
+ *     subroutine for ATExecAttachPartition to create/match indexes
+ *
+ * Enforce the indexing rule for partitioned tables during ALTER TABLE / ATTACH
+ * PARTITION: every partition must have an index attached to each index on the
+ * partitioned table.
+ */
+static void
+AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
+{
+   List       *idxes;
+   List       *attachRelIdxs;
+   Relation   *attachrelIdxRels;
+   IndexInfo **attachInfos;
+   int         i;
+   ListCell   *cell;
+   MemoryContext cxt;
+   MemoryContext oldcxt;
+
+   cxt = AllocSetContextCreate(CurrentMemoryContext,
+                               "AttachPartitionEnsureIndexes",
+                               ALLOCSET_DEFAULT_SIZES);
+   oldcxt = MemoryContextSwitchTo(cxt);
+
+   idxes = RelationGetIndexList(rel);
+   attachRelIdxs = RelationGetIndexList(attachrel);
+   attachrelIdxRels = palloc(sizeof(Relation) * list_length(attachRelIdxs));
+   attachInfos = palloc(sizeof(IndexInfo *) * list_length(attachRelIdxs));
+
+   /* Build arrays of all existing indexes and their IndexInfos */
+   i = 0;
+   foreach(cell, attachRelIdxs)
+   {
+       Oid         cldIdxId = lfirst_oid(cell);
+
+       attachrelIdxRels[i] = index_open(cldIdxId, AccessShareLock);
+       attachInfos[i] = BuildIndexInfo(attachrelIdxRels[i]);
+       i++;
+   }
+
+   /*
+    * For each index on the partitioned table, find a matching one in the
+    * partition-to-be; if one is not found, create one.
+    */
+   foreach(cell, idxes)
+   {
+       Oid         idx = lfirst_oid(cell);
+       Relation    idxRel = index_open(idx, AccessShareLock);
+       IndexInfo  *info;
+       AttrNumber *attmap;
+       bool        found = false;
+
+       /*
+        * Ignore indexes in the partitioned table other than partitioned
+        * indexes.
+        */
+       if (idxRel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
+       {
+           index_close(idxRel, AccessShareLock);
+           continue;
+       }
+
+       /* construct an indexinfo to compare existing indexes against */
+       info = BuildIndexInfo(idxRel);
+       attmap = convert_tuples_by_name_map(RelationGetDescr(attachrel),
+                                           RelationGetDescr(rel),
+                                           gettext_noop("could not convert row type"));
+
+       /*
+        * Scan the list of existing indexes in the partition-to-be, and mark
+        * the first matching, unattached one we find, if any, as partition of
+        * the parent index.  If we find one, we're done.
+        */
+       for (i = 0; i < list_length(attachRelIdxs); i++)
+       {
+           /* does this index have a parent?  if so, can't use it */
+           if (has_superclass(RelationGetRelid(attachrelIdxRels[i])))
+               continue;
+
+           if (CompareIndexInfo(attachInfos[i], info,
+                                attachrelIdxRels[i]->rd_indcollation,
+                                idxRel->rd_indcollation,
+                                attachrelIdxRels[i]->rd_opfamily,
+                                idxRel->rd_opfamily,
+                                attmap,
+                                RelationGetDescr(rel)->natts))
+           {
+               /* bingo. */
+               IndexSetParentIndex(attachrelIdxRels[i], idx);
+               found = true;
+               break;
+           }
+       }
+
+       /*
+        * If no suitable index was found in the partition-to-be, create one
+        * now.
+        */
+       if (!found)
+       {
+           IndexStmt  *stmt;
+
+           stmt = generateClonedIndexStmt(NULL, RelationGetRelid(attachrel),
+                                          idxRel, attmap,
+                                          RelationGetDescr(rel)->natts);
+           DefineIndex(RelationGetRelid(attachrel), stmt, InvalidOid,
+                       RelationGetRelid(idxRel),
+                       false, false, false, false, false);
+       }
+
+       index_close(idxRel, AccessShareLock);
+   }
+
+   /* Clean up. */
+   for (i = 0; i < list_length(attachRelIdxs); i++)
+       index_close(attachrelIdxRels[i], AccessShareLock);
+   MemoryContextSwitchTo(oldcxt);
+   MemoryContextDelete(cxt);
+}
+
 /*
  * ALTER TABLE DETACH PARTITION
  *
                new_repl[Natts_pg_class];
    ObjectAddress address;
    Oid         defaultPartOid;
+   List       *indexes;
+   ListCell   *cell;
 
    /*
     * We must lock the default partition, because detaching this partition
        }
    }
 
+   /* detach indexes too */
+   indexes = RelationGetIndexList(partRel);
+   foreach(cell, indexes)
+   {
+       Oid         idxid = lfirst_oid(cell);
+       Relation    idx;
+
+       if (!has_superclass(idxid))
+           continue;
+
+       Assert((IndexGetRelation(get_partition_parent(idxid), false) ==
+              RelationGetRelid(rel)));
+
+       idx = index_open(idxid, AccessExclusiveLock);
+       IndexSetParentIndex(idx, InvalidOid);
+       relation_close(idx, AccessExclusiveLock);
+   }
+
    /*
     * Invalidate the parent's relcache so that the partition is no longer
     * included in its partition descriptor.
 
    return address;
 }
+
+/*
+ * Before acquiring lock on an index, acquire the same lock on the owning
+ * table.
+ */
+struct AttachIndexCallbackState
+{
+   Oid     partitionOid;
+   Oid     parentTblOid;
+   bool    lockedParentTbl;
+};
+
+static void
+RangeVarCallbackForAttachIndex(const RangeVar *rv, Oid relOid, Oid oldRelOid,
+                              void *arg)
+{
+   struct AttachIndexCallbackState *state;
+   Form_pg_class classform;
+   HeapTuple   tuple;
+
+   state = (struct AttachIndexCallbackState *) arg;
+
+   if (!state->lockedParentTbl)
+   {
+       LockRelationOid(state->parentTblOid, AccessShareLock);
+       state->lockedParentTbl = true;
+   }
+
+   /*
+    * If we previously locked some other heap, and the name we're looking up
+    * no longer refers to an index on that relation, release the now-useless
+    * lock.  XXX maybe we should do *after* we verify whether the index does
+    * not actually belong to the same relation ...
+    */
+   if (relOid != oldRelOid && OidIsValid(state->partitionOid))
+   {
+       UnlockRelationOid(state->partitionOid, AccessShareLock);
+       state->partitionOid = InvalidOid;
+   }
+
+   /* Didn't find a relation, so no need for locking or permission checks. */
+   if (!OidIsValid(relOid))
+       return;
+
+   tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
+   if (!HeapTupleIsValid(tuple))
+       return;                 /* concurrently dropped, so nothing to do */
+   classform = (Form_pg_class) GETSTRUCT(tuple);
+   if (classform->relkind != RELKIND_PARTITIONED_INDEX &&
+       classform->relkind != RELKIND_INDEX)
+       ereport(ERROR,
+               (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                errmsg("\"%s\" is not an index", rv->relname)));
+   ReleaseSysCache(tuple);
+
+   /*
+    * Since we need only examine the heap's tupledesc, an access share lock
+    * on it (preventing any DDL) is sufficient.
+    */
+   state->partitionOid = IndexGetRelation(relOid, false);
+   LockRelationOid(state->partitionOid, AccessShareLock);
+}
+
+/*
+ * ALTER INDEX i1 ATTACH PARTITION i2
+ */
+static ObjectAddress
+ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
+{
+   Relation    partIdx;
+   Relation    partTbl;
+   Relation    parentTbl;
+   ObjectAddress address;
+   Oid         partIdxId;
+   Oid         currParent;
+   struct AttachIndexCallbackState state;
+
+   /*
+    * We need to obtain lock on the index 'name' to modify it, but we also
+    * need to read its owning table's tuple descriptor -- so we need to lock
+    * both.  To avoid deadlocks, obtain lock on the table before doing so on
+    * the index.  Furthermore, we need to examine the parent table of the
+    * partition, so lock that one too.
+    */
+   state.partitionOid = InvalidOid;
+   state.parentTblOid = parentIdx->rd_index->indrelid;
+   state.lockedParentTbl = false;
+   partIdxId =
+       RangeVarGetRelidExtended(name, AccessExclusiveLock, false, false,
+                                RangeVarCallbackForAttachIndex,
+                                (void *) &state);
+   /* Not there? */
+   if (!OidIsValid(partIdxId))
+       ereport(ERROR,
+               (errcode(ERRCODE_UNDEFINED_OBJECT),
+                errmsg("index \"%s\" does not exist", name->relname)));
+
+   /* no deadlock risk: RangeVarGetRelidExtended already acquired the lock */
+   partIdx = relation_open(partIdxId, AccessExclusiveLock);
+
+   /* we already hold locks on both tables, so this is safe: */
+   parentTbl = relation_open(parentIdx->rd_index->indrelid, AccessShareLock);
+   partTbl = relation_open(partIdx->rd_index->indrelid, NoLock);
+
+   ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partIdx));
+
+   /* Silently do nothing if already in the right state */
+   currParent = !has_superclass(partIdxId) ? InvalidOid :
+       get_partition_parent(partIdxId);
+   if (currParent != RelationGetRelid(parentIdx))
+   {
+       IndexInfo  *childInfo;
+       IndexInfo  *parentInfo;
+       AttrNumber *attmap;
+       bool        found;
+       int         i;
+       PartitionDesc partDesc;
+
+       /*
+        * If this partition already has an index attached, refuse the operation.
+        */
+       refuseDupeIndexAttach(parentIdx, partIdx, partTbl);
+
+       if (OidIsValid(currParent))
+           ereport(ERROR,
+                   (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                    errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
+                           RelationGetRelationName(partIdx),
+                           RelationGetRelationName(parentIdx)),
+                    errdetail("Index \"%s\" is already attached to another index.",
+                              RelationGetRelationName(partIdx))));
+
+       /* Make sure it indexes a partition of the other index's table */
+       partDesc = RelationGetPartitionDesc(parentTbl);
+       found = false;
+       for (i = 0; i < partDesc->nparts; i++)
+       {
+           if (partDesc->oids[i] == state.partitionOid)
+           {
+               found = true;
+               break;
+           }
+       }
+       if (!found)
+           ereport(ERROR,
+                   (errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
+                           RelationGetRelationName(partIdx),
+                           RelationGetRelationName(parentIdx)),
+                    errdetail("Index \"%s\" is not an index on any partition of table \"%s\".",
+                              RelationGetRelationName(partIdx),
+                              RelationGetRelationName(parentTbl))));
+
+       /* Ensure the indexes are compatible */
+       childInfo = BuildIndexInfo(partIdx);
+       parentInfo = BuildIndexInfo(parentIdx);
+       attmap = convert_tuples_by_name_map(RelationGetDescr(partTbl),
+                                           RelationGetDescr(parentTbl),
+                                           gettext_noop("could not convert row type"));
+       if (!CompareIndexInfo(childInfo, parentInfo,
+                             partIdx->rd_indcollation,
+                             parentIdx->rd_indcollation,
+                             partIdx->rd_opfamily,
+                             parentIdx->rd_opfamily,
+                             attmap,
+                             RelationGetDescr(partTbl)->natts))
+           ereport(ERROR,
+                   (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                    errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
+                           RelationGetRelationName(partIdx),
+                           RelationGetRelationName(parentIdx)),
+                    errdetail("The index definitions do not match.")));
+
+       /* All good -- do it */
+       IndexSetParentIndex(partIdx, RelationGetRelid(parentIdx));
+       pfree(attmap);
+
+       CommandCounterIncrement();
+
+       validatePartitionedIndex(parentIdx, parentTbl);
+   }
+
+   relation_close(parentTbl, AccessShareLock);
+   /* keep these locks till commit */
+   relation_close(partTbl, NoLock);
+   relation_close(partIdx, NoLock);
+
+   return address;
+}
+
+/*
+ * Verify whether the given partition already contains an index attached
+ * to the given partitioned index.  If so, raise an error.
+ */
+static void
+refuseDupeIndexAttach(Relation parentIdx, Relation partIdx, Relation partitionTbl)
+{
+   Relation        pg_inherits;
+   ScanKeyData     key;
+   HeapTuple       tuple;
+   SysScanDesc     scan;
+
+   pg_inherits = heap_open(InheritsRelationId, AccessShareLock);
+   ScanKeyInit(&key, Anum_pg_inherits_inhparent,
+               BTEqualStrategyNumber, F_OIDEQ,
+               ObjectIdGetDatum(RelationGetRelid(parentIdx)));
+   scan = systable_beginscan(pg_inherits, InheritsParentIndexId, true,
+                             NULL, 1, &key);
+   while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+   {
+       Form_pg_inherits    inhForm;
+       Oid         tab;
+
+       inhForm = (Form_pg_inherits) GETSTRUCT(tuple);
+       tab = IndexGetRelation(inhForm->inhrelid, false);
+       if (tab == RelationGetRelid(partitionTbl))
+           ereport(ERROR,
+                   (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                    errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
+                           RelationGetRelationName(partIdx),
+                           RelationGetRelationName(parentIdx)),
+                    errdetail("Another index is already attached for partition \"%s\".",
+                              RelationGetRelationName(partitionTbl))));
+   }
+
+   systable_endscan(scan);
+   heap_close(pg_inherits, AccessShareLock);
+}
+
+/*
+ * Verify whether the set of attached partition indexes to a parent index on
+ * a partitioned table is complete.  If it is, mark the parent index valid.
+ *
+ * This should be called each time a partition index is attached.
+ */
+static void
+validatePartitionedIndex(Relation partedIdx, Relation partedTbl)
+{
+   Relation        inheritsRel;
+   SysScanDesc     scan;
+   ScanKeyData     key;
+   int             tuples = 0;
+   HeapTuple       inhTup;
+   bool            updated = false;
+
+   Assert(partedIdx->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
+
+   /*
+    * Scan pg_inherits for this parent index.  Count each valid index we find
+    * (verifying the pg_index entry for each), and if we reach the total
+    * amount we expect, we can mark this parent index as valid.
+    */
+   inheritsRel = heap_open(InheritsRelationId, AccessShareLock);
+   ScanKeyInit(&key, Anum_pg_inherits_inhparent,
+               BTEqualStrategyNumber, F_OIDEQ,
+               ObjectIdGetDatum(RelationGetRelid(partedIdx)));
+   scan = systable_beginscan(inheritsRel, InheritsParentIndexId, true,
+                             NULL, 1, &key);
+   while ((inhTup = systable_getnext(scan)) != NULL)
+   {
+       Form_pg_inherits inhForm = (Form_pg_inherits) GETSTRUCT(inhTup);
+       HeapTuple       indTup;
+       Form_pg_index   indexForm;
+
+       indTup = SearchSysCache1(INDEXRELID,
+                               ObjectIdGetDatum(inhForm->inhrelid));
+       if (!indTup)
+           elog(ERROR, "cache lookup failed for index %u",
+                inhForm->inhrelid);
+       indexForm = (Form_pg_index) GETSTRUCT(indTup);
+       if (IndexIsValid(indexForm))
+           tuples += 1;
+       ReleaseSysCache(indTup);
+   }
+
+   /* Done with pg_inherits */
+   systable_endscan(scan);
+   heap_close(inheritsRel, AccessShareLock);
+
+   /*
+    * If we found as many inherited indexes as the partitioned table has
+    * partitions, we're good; update pg_index to set indisvalid.
+    */
+   if (tuples == RelationGetPartitionDesc(partedTbl)->nparts)
+   {
+       Relation    idxRel;
+       HeapTuple   newtup;
+
+       idxRel = heap_open(IndexRelationId, RowExclusiveLock);
+
+       newtup = heap_copytuple(partedIdx->rd_indextuple);
+       ((Form_pg_index) GETSTRUCT(newtup))->indisvalid = true;
+       updated = true;
+
+       CatalogTupleUpdate(idxRel, &partedIdx->rd_indextuple->t_self, newtup);
+
+       heap_close(idxRel, RowExclusiveLock);
+   }
+
+   /*
+    * If this index is in turn a partition of a larger index, validating it
+    * might cause the parent to become valid also.  Try that.
+    */
+   if (updated &&
+       has_superclass(RelationGetRelid(partedIdx)))
+   {
+       Oid         parentIdxId,
+                   parentTblId;
+       Relation    parentIdx,
+                   parentTbl;
+
+       /* make sure we see the validation we just did */
+       CommandCounterIncrement();
+
+       parentIdxId = get_partition_parent(RelationGetRelid(partedIdx));
+       parentTblId = get_partition_parent(RelationGetRelid(partedTbl));
+       parentIdx = relation_open(parentIdxId, AccessExclusiveLock);
+       parentTbl = relation_open(parentTblId, AccessExclusiveLock);
+       Assert(!parentIdx->rd_index->indisvalid);
+
+       validatePartitionedIndex(parentIdx, parentTbl);
+
+       relation_close(parentIdx, AccessExclusiveLock);
+       relation_close(parentTbl, AccessExclusiveLock);
+   }
+}
 
 
    COPY_STRING_FIELD(idxname);
    COPY_NODE_FIELD(relation);
+   COPY_SCALAR_FIELD(relationId);
    COPY_STRING_FIELD(accessMethod);
    COPY_STRING_FIELD(tableSpace);
    COPY_NODE_FIELD(indexParams);
 
 {
    COMPARE_STRING_FIELD(idxname);
    COMPARE_NODE_FIELD(relation);
+   COMPARE_SCALAR_FIELD(relationId);
    COMPARE_STRING_FIELD(accessMethod);
    COMPARE_STRING_FIELD(tableSpace);
    COMPARE_NODE_FIELD(indexParams);
 
 
    WRITE_STRING_FIELD(idxname);
    WRITE_NODE_FIELD(relation);
+   WRITE_OID_FIELD(relationId);
    WRITE_STRING_FIELD(accessMethod);
    WRITE_STRING_FIELD(tableSpace);
    WRITE_NODE_FIELD(indexParams);
 
 %type <ival>   add_drop opt_asc_desc opt_nulls_order
 
 %type <node>   alter_table_cmd alter_type_cmd opt_collate_clause
-      replica_identity partition_cmd
+      replica_identity partition_cmd index_partition_cmd
 %type <list>   alter_table_cmds alter_type_cmds
 %type <list>    alter_identity_column_option_list
 %type <defelt>  alter_identity_column_option
                    n->missing_ok = true;
                    $$ = (Node *)n;
                }
+       |   ALTER INDEX qualified_name index_partition_cmd
+               {
+                   AlterTableStmt *n = makeNode(AlterTableStmt);
+                   n->relation = $3;
+                   n->cmds = list_make1($4);
+                   n->relkind = OBJECT_INDEX;
+                   n->missing_ok = false;
+                   $$ = (Node *)n;
+               }
        |   ALTER INDEX ALL IN_P TABLESPACE name SET TABLESPACE name opt_nowait
                {
                    AlterTableMoveAllStmt *n =
                }
        ;
 
+index_partition_cmd:
+           /* ALTER INDEX <name> ATTACH PARTITION <index_name> */
+           ATTACH PARTITION qualified_name
+               {
+                   AlterTableCmd *n = makeNode(AlterTableCmd);
+                   PartitionCmd *cmd = makeNode(PartitionCmd);
+
+                   n->subtype = AT_AttachPartition;
+                   cmd->name = $3;
+                   cmd->bound = NULL;
+                   n->def = (Node *) cmd;
+
+                   $$ = (Node *) n;
+               }
+       ;
+
 alter_table_cmd:
            /* ALTER TABLE <name> ADD <coldef> */
            ADD_P columnDef
  *****************************************************************************/
 
 IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
-           ON qualified_name access_method_clause '(' index_params ')'
+           ON relation_expr access_method_clause '(' index_params ')'
            opt_reloptions OptTableSpace where_clause
                {
                    IndexStmt *n = makeNode(IndexStmt);
                    n->concurrent = $4;
                    n->idxname = $5;
                    n->relation = $7;
+                   n->relationId = InvalidOid;
                    n->accessMethod = $8;
                    n->indexParams = $10;
                    n->options = $12;
                    $$ = (Node *)n;
                }
            | CREATE opt_unique INDEX opt_concurrently IF_P NOT EXISTS index_name
-           ON qualified_name access_method_clause '(' index_params ')'
+           ON relation_expr access_method_clause '(' index_params ')'
            opt_reloptions OptTableSpace where_clause
                {
                    IndexStmt *n = makeNode(IndexStmt);
                    n->concurrent = $4;
                    n->idxname = $8;
                    n->relation = $10;
+                   n->relationId = InvalidOid;
                    n->accessMethod = $11;
                    n->indexParams = $13;
                    n->options = $15;
 
                         TableLikeClause *table_like_clause);
 static void transformOfType(CreateStmtContext *cxt,
                TypeName *ofTypename);
-static IndexStmt *generateClonedIndexStmt(CreateStmtContext *cxt,
-                       Relation source_idx,
-                       const AttrNumber *attmap, int attmap_length);
 static List *get_collation(Oid collation, Oid actual_datatype);
 static List *get_opclass(Oid opclass, Oid actual_datatype);
 static void transformIndexConstraints(CreateStmtContext *cxt);
            parent_index = index_open(parent_index_oid, AccessShareLock);
 
            /* Build CREATE INDEX statement to recreate the parent_index */
-           index_stmt = generateClonedIndexStmt(cxt, parent_index,
+           index_stmt = generateClonedIndexStmt(cxt->relation, InvalidOid,
+                                                parent_index,
                                                 attmap, tupleDesc->natts);
 
            /* Copy comment on index, if requested */
 
 /*
  * Generate an IndexStmt node using information from an already existing index
- * "source_idx".  Attribute numbers should be adjusted according to attmap.
+ * "source_idx", for the rel identified either by heapRel or heapRelid.
+ *
+ * Attribute numbers should be adjusted according to attmap.
  */
-static IndexStmt *
-generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
+IndexStmt *
+generateClonedIndexStmt(RangeVar *heapRel, Oid heapRelid, Relation source_idx,
                        const AttrNumber *attmap, int attmap_length)
 {
    Oid         source_relid = RelationGetRelid(source_idx);
    Datum       datum;
    bool        isnull;
 
+   Assert((heapRel == NULL && OidIsValid(heapRelid)) ||
+          (heapRel != NULL && !OidIsValid(heapRelid)));
+
    /*
     * Fetch pg_class tuple of source index.  We can't use the copy in the
     * relcache entry because it doesn't include optional fields.
 
    /* Begin building the IndexStmt */
    index = makeNode(IndexStmt);
-   index->relation = cxt->relation;
+   index->relation = heapRel;
+   index->relationId = heapRelid;
    index->accessMethod = pstrdup(NameStr(amrec->amname));
    if (OidIsValid(idxrelrec->reltablespace))
        index->tableSpace = get_tablespace_name(idxrelrec->reltablespace);
 {
    Relation    parentRel = cxt->rel;
 
-   /* the table must be partitioned */
-   if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
-       ereport(ERROR,
-               (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
-                errmsg("\"%s\" is not partitioned",
-                       RelationGetRelationName(parentRel))));
-
-   /* transform the partition bound, if any */
-   Assert(RelationGetPartitionKey(parentRel) != NULL);
-   if (cmd->bound != NULL)
-       cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,
-                                                cmd->bound);
+   switch (parentRel->rd_rel->relkind)
+   {
+       case RELKIND_PARTITIONED_TABLE:
+           /* transform the partition bound, if any */
+           Assert(RelationGetPartitionKey(parentRel) != NULL);
+           if (cmd->bound != NULL)
+               cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,
+                                                        cmd->bound);
+           break;
+       case RELKIND_PARTITIONED_INDEX:
+           /* nothing to check */
+           Assert(cmd->bound == NULL);
+           break;
+       case RELKIND_RELATION:
+           /* the table must be partitioned */
+           ereport(ERROR,
+                   (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                    errmsg("table \"%s\" is not partitioned",
+                           RelationGetRelationName(parentRel))));
+           break;
+       case RELKIND_INDEX:
+           /* the index must be partitioned */
+           ereport(ERROR,
+                   (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                    errmsg("index \"%s\" is not partitioned",
+                           RelationGetRelationName(parentRel))));
+           break;
+       default:
+           /* parser shouldn't let this case through */
+           elog(ERROR, "\"%s\" is not a partitioned table or index",
+                RelationGetRelationName(parentRel));
+           break;
+   }
 }
 
 /*
 
 #include "access/xlog.h"
 #include "catalog/catalog.h"
 #include "catalog/namespace.h"
+#include "catalog/pg_inherits_fn.h"
 #include "catalog/toasting.h"
 #include "commands/alter.h"
 #include "commands/async.h"
                    IndexStmt  *stmt = (IndexStmt *) parsetree;
                    Oid         relid;
                    LOCKMODE    lockmode;
+                   List       *inheritors = NIL;
 
                    if (stmt->concurrent)
                        PreventTransactionChain(isTopLevel,
                                                 RangeVarCallbackOwnsRelation,
                                                 NULL);
 
+                   /*
+                    * CREATE INDEX on partitioned tables (but not regular
+                    * inherited tables) recurses to partitions, so we must
+                    * acquire locks early to avoid deadlocks.
+                    */
+                   if (stmt->relation->inh)
+                   {
+                       Relation    rel;
+
+                       /* already locked by RangeVarGetRelidExtended */
+                       rel = heap_open(relid, NoLock);
+                       if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+                           inheritors = find_all_inheritors(relid, lockmode,
+                                                            NULL);
+                       heap_close(rel, NoLock);
+                   }
+
                    /* Run parse analysis ... */
                    stmt = transformIndexStmt(relid, stmt, queryString);
 
                        DefineIndex(relid,  /* OID of heap relation */
                                    stmt,
                                    InvalidOid, /* no predefined OID */
+                                   InvalidOid, /* no parent index */
                                    false,  /* is_alter_table */
                                    true,   /* check_rights */
                                    true,   /* check_not_in_use */
                                                     parsetree);
                    commandCollected = true;
                    EventTriggerAlterTableEnd();
+
+                   list_free(inheritors);
                }
                break;
 
 
        if (!HeapTupleIsValid(tuple))
            PG_RETURN_NULL();
        rd_rel = (Form_pg_class) GETSTRUCT(tuple);
-       if (rd_rel->relkind != RELKIND_INDEX)
+       if (rd_rel->relkind != RELKIND_INDEX &&
+           rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
        {
            ReleaseSysCache(tuple);
            PG_RETURN_NULL();
 
 static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
 static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
                       const Oid *excludeOps,
-                      bool attrsOnly, bool showTblSpc,
+                      bool attrsOnly, bool showTblSpc, bool inherits,
                       int prettyFlags, bool missing_ok);
 static char *pg_get_statisticsobj_worker(Oid statextid, bool missing_ok);
 static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags,
 
    prettyFlags = PRETTYFLAG_INDENT;
 
-   res = pg_get_indexdef_worker(indexrelid, 0, NULL, false, false,
+   res = pg_get_indexdef_worker(indexrelid, 0, NULL, false, false, false,
                                 prettyFlags, true);
 
    if (res == NULL)
    prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
 
    res = pg_get_indexdef_worker(indexrelid, colno, NULL, colno != 0, false,
-                                prettyFlags, true);
+                                false, prettyFlags, true);
 
    if (res == NULL)
        PG_RETURN_NULL();
 char *
 pg_get_indexdef_string(Oid indexrelid)
 {
-   return pg_get_indexdef_worker(indexrelid, 0, NULL, false, true, 0, false);
+   return pg_get_indexdef_worker(indexrelid, 0, NULL, false, true, true, 0, false);
 }
 
 /* Internal version that just reports the column definitions */
    int         prettyFlags;
 
    prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
-   return pg_get_indexdef_worker(indexrelid, 0, NULL, true, false,
+   return pg_get_indexdef_worker(indexrelid, 0, NULL, true, false, false,
                                  prettyFlags, false);
 }
 
 static char *
 pg_get_indexdef_worker(Oid indexrelid, int colno,
                       const Oid *excludeOps,
-                      bool attrsOnly, bool showTblSpc,
+                      bool attrsOnly, bool showTblSpc, bool inherits,
                       int prettyFlags, bool missing_ok)
 {
    /* might want a separate isConstraint parameter later */
    if (!attrsOnly)
    {
        if (!isConstraint)
-           appendStringInfo(&buf, "CREATE %sINDEX %s ON %s USING %s (",
+           appendStringInfo(&buf, "CREATE %sINDEX %s ON %s%s USING %s (",
                             idxrec->indisunique ? "UNIQUE " : "",
                             quote_identifier(NameStr(idxrelrec->relname)),
+                            idxrelrec->relkind == RELKIND_PARTITIONED_INDEX
+                            && !inherits ? "ONLY " : "",
                             generate_relation_name(indrelid, NIL),
                             quote_identifier(NameStr(amrec->amname)));
        else                    /* currently, must be EXCLUDE constraint */
                                                              operators,
                                                              false,
                                                              false,
+                                                             false,
                                                              prettyFlags,
                                                              false));
                break;
 
 RelationParseRelOptions(Relation relation, HeapTuple tuple)
 {
    bytea      *options;
+   amoptions_function amoptsfn;
 
    relation->rd_options = NULL;
 
-   /* Fall out if relkind should not have options */
+   /*
+    * Look up any AM-specific parse function; fall out if relkind should not
+    * have options.
+    */
    switch (relation->rd_rel->relkind)
    {
        case RELKIND_RELATION:
        case RELKIND_TOASTVALUE:
-       case RELKIND_INDEX:
        case RELKIND_VIEW:
        case RELKIND_MATVIEW:
        case RELKIND_PARTITIONED_TABLE:
+           amoptsfn = NULL;
+           break;
+       case RELKIND_INDEX:
+       case RELKIND_PARTITIONED_INDEX:
+           amoptsfn = relation->rd_amroutine->amoptions;
            break;
        default:
            return;
     * we might not have any other for pg_class yet (consider executing this
     * code for pg_class itself)
     */
-   options = extractRelOptions(tuple,
-                               GetPgClassDescriptor(),
-                               relation->rd_rel->relkind == RELKIND_INDEX ?
-                               relation->rd_amroutine->amoptions : NULL);
+   options = extractRelOptions(tuple, GetPgClassDescriptor(), amoptsfn);
 
    /*
     * Copy parsed data into CacheMemoryContext.  To guard against the
             * and we don't want to use the full-blown procedure because it's
             * a headache for indexes that reload itself depends on.
             */
-           if (rd->rd_rel->relkind == RELKIND_INDEX)
+           if (rd->rd_rel->relkind == RELKIND_INDEX ||
+               rd->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
                RelationReloadIndexInfo(rd);
            else
                RelationClearRelation(rd, true);
    Form_pg_class relp;
 
    /* Should be called only for invalidated indexes */
-   Assert(relation->rd_rel->relkind == RELKIND_INDEX &&
+   Assert((relation->rd_rel->relkind == RELKIND_INDEX ||
+           relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) &&
           !relation->rd_isvalid);
 
    /* Ensure it's closed at smgr level */
    {
        RelationInitPhysicalAddr(relation);
 
-       if (relation->rd_rel->relkind == RELKIND_INDEX)
+       if (relation->rd_rel->relkind == RELKIND_INDEX ||
+           relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
        {
            relation->rd_isvalid = false;   /* needs to be revalidated */
            if (relation->rd_refcnt > 1 && IsTransactionState())
     * re-read the pg_class row to handle possible physical relocation of the
     * index, and we check for pg_index updates too.
     */
-   if (relation->rd_rel->relkind == RELKIND_INDEX &&
+   if ((relation->rd_rel->relkind == RELKIND_INDEX ||
+        relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) &&
        relation->rd_refcnt > 0 &&
        relation->rd_indexcxt != NULL)
    {
            rel->rd_att->constr = constr;
        }
 
-       /* If it's an index, there's more to do */
+       /*
+        * If it's an index, there's more to do.  Note we explicitly ignore
+        * partitioned indexes here.
+        */
        if (rel->rd_rel->relkind == RELKIND_INDEX)
        {
            MemoryContext indexcxt;
                   (rel->rd_options ? VARSIZE(rel->rd_options) : 0),
                   fp);
 
-       /* If it's an index, there's more to do */
+       /*
+        * If it's an index, there's more to do. Note we explicitly ignore
+        * partitioned indexes here.
+        */
        if (rel->rd_rel->relkind == RELKIND_INDEX)
        {
            /* write the pg_index tuple */
 
 
 static void flagInhTables(Archive *fout, TableInfo *tbinfo, int numTables,
              InhInfo *inhinfo, int numInherits);
+static void flagInhIndexes(Archive *fout, TableInfo *tblinfo, int numTables);
 static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
 static DumpableObject **buildIndexArray(void *objArray, int numObjs,
                Size objSize);
 static void findParentsByOid(TableInfo *self,
                 InhInfo *inhinfo, int numInherits);
 static int strInArray(const char *pattern, char **arr, int arr_size);
+static IndxInfo *findIndexByOid(Oid oid, DumpableObject **idxinfoindex,
+              int numIndexes);
 
 
 /*
        write_msg(NULL, "reading indexes\n");
    getIndexes(fout, tblinfo, numTables);
 
+   if (g_verbose)
+       write_msg(NULL, "flagging indexes in partitioned tables\n");
+   flagInhIndexes(fout, tblinfo, numTables);
+
    if (g_verbose)
        write_msg(NULL, "reading extended statistics\n");
    getExtendedStatistics(fout, tblinfo, numTables);
        if (find_parents)
            findParentsByOid(&tblinfo[i], inhinfo, numInherits);
 
-       /* If needed, mark the parents as interesting for getTableAttrs. */
+       /*
+        * If needed, mark the parents as interesting for getTableAttrs
+        * and getIndexes.
+        */
        if (mark_parents)
        {
            int         numParents = tblinfo[i].numParents;
    }
 }
 
+/*
+ * flagInhIndexes -
+ *  Create AttachIndexInfo objects for partitioned indexes, and add
+ *  appropriate dependency links.
+ */
+static void
+flagInhIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
+{
+   int     i,
+           j,
+           k;
+   DumpableObject ***parentIndexArray;
+
+   parentIndexArray = (DumpableObject ***)
+       pg_malloc0(getMaxDumpId() * sizeof(DumpableObject **));
+
+   for (i = 0; i < numTables; i++)
+   {
+       TableInfo      *parenttbl;
+       IndexAttachInfo *attachinfo;
+
+       if (!tblinfo[i].ispartition || tblinfo[i].numParents == 0)
+           continue;
+
+       Assert(tblinfo[i].numParents == 1);
+       parenttbl = tblinfo[i].parents[0];
+
+       /*
+        * We need access to each parent table's index list, but there is no
+        * index to cover them outside of this function.  To avoid having to
+        * sort every parent table's indexes each time we come across each of
+        * its partitions, create an indexed array for each parent the first
+        * time it is required.
+        */
+       if (parentIndexArray[parenttbl->dobj.dumpId] == NULL)
+           parentIndexArray[parenttbl->dobj.dumpId] =
+               buildIndexArray(parenttbl->indexes,
+                               parenttbl->numIndexes,
+                               sizeof(IndxInfo));
+
+       attachinfo = (IndexAttachInfo *)
+           pg_malloc0(tblinfo[i].numIndexes * sizeof(IndexAttachInfo));
+       for (j = 0, k = 0; j < tblinfo[i].numIndexes; j++)
+       {
+           IndxInfo   *index = &(tblinfo[i].indexes[j]);
+           IndxInfo   *parentidx;
+
+           if (index->parentidx == 0)
+               continue;
+
+           parentidx = findIndexByOid(index->parentidx,
+                                      parentIndexArray[parenttbl->dobj.dumpId],
+                                      parenttbl->numIndexes);
+           if (parentidx == NULL)
+               continue;
+
+           attachinfo[k].dobj.objType = DO_INDEX_ATTACH;
+           attachinfo[k].dobj.catId.tableoid = 0;
+           attachinfo[k].dobj.catId.oid = 0;
+           AssignDumpId(&attachinfo[k].dobj);
+           attachinfo[k].dobj.name = pg_strdup(index->dobj.name);
+           attachinfo[k].parentIdx = parentidx;
+           attachinfo[k].partitionIdx = index;
+
+           /*
+            * We want dependencies from parent to partition (so that the
+            * partition index is created first), and another one from
+            * attach object to parent (so that the partition index is
+            * attached once the parent index has been created).
+            */
+           addObjectDependency(&parentidx->dobj, index->dobj.dumpId);
+           addObjectDependency(&attachinfo[k].dobj, parentidx->dobj.dumpId);
+
+           k++;
+       }
+   }
+
+   for (i = 0; i < numTables; i++)
+       if (parentIndexArray[i])
+           pg_free(parentIndexArray[i]);
+   pg_free(parentIndexArray);
+}
+
 /* flagInhAttrs -
  *  for each dumpable table in tblinfo, flag its inherited attributes
  *
    return (ExtensionInfo *) findObjectByOid(oid, extinfoindex, numExtensions);
 }
 
+/*
+ * findIndexByOid
+ *     find the entry of the index with the given oid
+ *
+ * This one's signature is different from the previous ones because we lack a
+ * global array of all indexes, so caller must pass their array as argument.
+ */
+static IndxInfo *
+findIndexByOid(Oid oid, DumpableObject **idxinfoindex, int numIndexes)
+{
+   return (IndxInfo *) findObjectByOid(oid, idxinfoindex, numIndexes);
+}
 
 /*
  * setExtensionMembership
 
 static void dumpSequence(Archive *fout, TableInfo *tbinfo);
 static void dumpSequenceData(Archive *fout, TableDataInfo *tdinfo);
 static void dumpIndex(Archive *fout, IndxInfo *indxinfo);
+static void dumpIndexAttach(Archive *fout, IndexAttachInfo *attachinfo);
 static void dumpStatisticsExt(Archive *fout, StatsExtInfo *statsextinfo);
 static void dumpConstraint(Archive *fout, ConstraintInfo *coninfo);
 static void dumpTableConstraintComment(Archive *fout, ConstraintInfo *coninfo);
    int         i_tableoid,
                i_oid,
                i_indexname,
+               i_parentidx,
                i_indexdef,
                i_indnkeys,
                i_indkey,
    {
        TableInfo  *tbinfo = &tblinfo[i];
 
-       /* Only plain tables and materialized views have indexes. */
-       if (tbinfo->relkind != RELKIND_RELATION &&
-           tbinfo->relkind != RELKIND_MATVIEW)
-           continue;
        if (!tbinfo->hasindex)
            continue;
 
-       /* Ignore indexes of tables whose definitions are not to be dumped */
-       if (!(tbinfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+       /*
+        * Ignore indexes of tables whose definitions are not to be dumped.
+        *
+        * We also need indexes on partitioned tables which have partitions to
+        * be dumped, in order to dump the indexes on the partitions.
+        */
+       if (!(tbinfo->dobj.dump & DUMP_COMPONENT_DEFINITION) &&
+           !tbinfo->interesting)
            continue;
 
        if (g_verbose)
         * is not.
         */
        resetPQExpBuffer(query);
-       if (fout->remoteVersion >= 90400)
+       if (fout->remoteVersion >= 11000)
+       {
+           appendPQExpBuffer(query,
+                             "SELECT t.tableoid, t.oid, "
+                             "t.relname AS indexname, "
+                             "inh.inhparent AS parentidx, "
+                             "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
+                             "t.relnatts AS indnkeys, "
+                             "i.indkey, i.indisclustered, "
+                             "i.indisreplident, t.relpages, "
+                             "c.contype, c.conname, "
+                             "c.condeferrable, c.condeferred, "
+                             "c.tableoid AS contableoid, "
+                             "c.oid AS conoid, "
+                             "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "
+                             "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
+                             "t.reloptions AS indreloptions "
+                             "FROM pg_catalog.pg_index i "
+                             "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "
+                             "JOIN pg_catalog.pg_class t2 ON (t2.oid = i.indrelid) "
+                             "LEFT JOIN pg_catalog.pg_constraint c "
+                             "ON (i.indrelid = c.conrelid AND "
+                             "i.indexrelid = c.conindid AND "
+                             "c.contype IN ('p','u','x')) "
+                             "LEFT JOIN pg_catalog.pg_inherits inh "
+                             "ON (inh.inhrelid = indexrelid) "
+                             "WHERE i.indrelid = '%u'::pg_catalog.oid "
+                             "AND (i.indisvalid OR t2.relkind = 'p') "
+                             "AND i.indisready "
+                             "ORDER BY indexname",
+                             tbinfo->dobj.catId.oid);
+       }
+       else if (fout->remoteVersion >= 90400)
        {
            /*
             * the test on indisready is necessary in 9.2, and harmless in
            appendPQExpBuffer(query,
                              "SELECT t.tableoid, t.oid, "
                              "t.relname AS indexname, "
+                             "0 AS parentidx, "
                              "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
                              "t.relnatts AS indnkeys, "
                              "i.indkey, i.indisclustered, "
            appendPQExpBuffer(query,
                              "SELECT t.tableoid, t.oid, "
                              "t.relname AS indexname, "
+                             "0 AS parentidx, "
                              "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
                              "t.relnatts AS indnkeys, "
                              "i.indkey, i.indisclustered, "
            appendPQExpBuffer(query,
                              "SELECT t.tableoid, t.oid, "
                              "t.relname AS indexname, "
+                             "0 AS parentidx, "
                              "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
                              "t.relnatts AS indnkeys, "
                              "i.indkey, i.indisclustered, "
            appendPQExpBuffer(query,
                              "SELECT t.tableoid, t.oid, "
                              "t.relname AS indexname, "
+                             "0 AS parentidx, "
                              "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
                              "t.relnatts AS indnkeys, "
                              "i.indkey, i.indisclustered, "
        i_tableoid = PQfnumber(res, "tableoid");
        i_oid = PQfnumber(res, "oid");
        i_indexname = PQfnumber(res, "indexname");
+       i_parentidx = PQfnumber(res, "parentidx");
        i_indexdef = PQfnumber(res, "indexdef");
        i_indnkeys = PQfnumber(res, "indnkeys");
        i_indkey = PQfnumber(res, "indkey");
        i_tablespace = PQfnumber(res, "tablespace");
        i_indreloptions = PQfnumber(res, "indreloptions");
 
-       indxinfo = (IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo));
+       tbinfo->indexes = indxinfo =
+           (IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo));
        constrinfo = (ConstraintInfo *) pg_malloc(ntups * sizeof(ConstraintInfo));
+       tbinfo->numIndexes = ntups;
 
        for (j = 0; j < ntups; j++)
        {
            indxinfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_tableoid));
            indxinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
            AssignDumpId(&indxinfo[j].dobj);
+           indxinfo[j].dobj.dump = tbinfo->dobj.dump;
            indxinfo[j].dobj.name = pg_strdup(PQgetvalue(res, j, i_indexname));
            indxinfo[j].dobj.namespace = tbinfo->dobj.namespace;
            indxinfo[j].indextable = tbinfo;
                          indxinfo[j].indkeys, indxinfo[j].indnkeys);
            indxinfo[j].indisclustered = (PQgetvalue(res, j, i_indisclustered)[0] == 't');
            indxinfo[j].indisreplident = (PQgetvalue(res, j, i_indisreplident)[0] == 't');
+           indxinfo[j].parentidx = atooid(PQgetvalue(res, j, i_parentidx));
            indxinfo[j].relpages = atoi(PQgetvalue(res, j, i_relpages));
            contype = *(PQgetvalue(res, j, i_contype));
 
                constrinfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_contableoid));
                constrinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_conoid));
                AssignDumpId(&constrinfo[j].dobj);
+               constrinfo[j].dobj.dump = tbinfo->dobj.dump;
                constrinfo[j].dobj.name = pg_strdup(PQgetvalue(res, j, i_conname));
                constrinfo[j].dobj.namespace = tbinfo->dobj.namespace;
                constrinfo[j].contable = tbinfo;
        case DO_INDEX:
            dumpIndex(fout, (IndxInfo *) dobj);
            break;
+       case DO_INDEX_ATTACH:
+           dumpIndexAttach(fout, (IndexAttachInfo *) dobj);
+           break;
        case DO_STATSEXT:
            dumpStatisticsExt(fout, (StatsExtInfo *) dobj);
            break;
    destroyPQExpBuffer(labelq);
 }
 
+/*
+ * dumpIndexAttach
+ *   write out to fout a partitioned-index attachment clause
+ */
+void
+dumpIndexAttach(Archive *fout, IndexAttachInfo *attachinfo)
+{
+   if (fout->dopt->dataOnly)
+       return;
+
+   if (attachinfo->partitionIdx->dobj.dump & DUMP_COMPONENT_DEFINITION)
+   {
+       PQExpBuffer q = createPQExpBuffer();
+
+       appendPQExpBuffer(q, "\nALTER INDEX %s ",
+                         fmtQualifiedId(fout->remoteVersion,
+                                        attachinfo->parentIdx->dobj.namespace->dobj.name,
+                                        attachinfo->parentIdx->dobj.name));
+       appendPQExpBuffer(q, "ATTACH PARTITION %s;\n",
+                         fmtQualifiedId(fout->remoteVersion,
+                                        attachinfo->partitionIdx->dobj.namespace->dobj.name,
+                                        attachinfo->partitionIdx->dobj.name));
+
+       ArchiveEntry(fout, attachinfo->dobj.catId, attachinfo->dobj.dumpId,
+                    attachinfo->dobj.name,
+                    NULL, NULL,
+                    "",
+                    false, "INDEX ATTACH", SECTION_POST_DATA,
+                    q->data, "", NULL,
+                    NULL, 0,
+                    NULL, NULL);
+
+       destroyPQExpBuffer(q);
+   }
+}
+
 /*
  * dumpStatisticsExt
  *   write out to fout an extended statistics object
                addObjectDependency(postDataBound, dobj->dumpId);
                break;
            case DO_INDEX:
+           case DO_INDEX_ATTACH:
            case DO_STATSEXT:
            case DO_REFRESH_MATVIEW:
            case DO_TRIGGER:
 
    DO_TABLE,
    DO_ATTRDEF,
    DO_INDEX,
+   DO_INDEX_ATTACH,
    DO_STATSEXT,
    DO_RULE,
    DO_TRIGGER,
     */
    int         numParents;     /* number of (immediate) parent tables */
    struct _tableInfo **parents;    /* TableInfos of immediate parents */
+   int         numIndexes;     /* number of indexes */
+   struct _indxInfo *indexes;  /* indexes */
    struct _tableDataInfo *dataObj; /* TableDataInfo, if dumping its data */
    int         numTriggers;    /* number of triggers for table */
    struct _triggerInfo *triggers;  /* array of TriggerInfo structs */
    Oid        *indkeys;
    bool        indisclustered;
    bool        indisreplident;
+   Oid         parentidx;      /* if partitioned, parent index OID */
    /* if there is an associated constraint object, its dumpId: */
    DumpId      indexconstraint;
    int         relpages;       /* relpages of the underlying table */
 } IndxInfo;
 
+typedef struct _indexAttachInfo
+{
+   DumpableObject dobj;
+   IndxInfo   *parentIdx;      /* link to index on partitioned table */
+   IndxInfo   *partitionIdx;   /* link to index on partition */
+} IndexAttachInfo;
+
 typedef struct _statsExtInfo
 {
    DumpableObject dobj;
 
  * pg_dump.c; that is, PRE_DATA objects must sort before DO_PRE_DATA_BOUNDARY,
  * POST_DATA objects must sort after DO_POST_DATA_BOUNDARY, and DATA objects
  * must sort between them.
+ *
+ * Note: sortDataAndIndexObjectsBySize wants to have all DO_TABLE_DATA and
+ * DO_INDEX objects in contiguous chunks, so do not reuse the values for those
+ * for other object types.
  */
 static const int dbObjectTypePriority[] =
 {
    18,                         /* DO_TABLE */
    20,                         /* DO_ATTRDEF */
    28,                         /* DO_INDEX */
-   29,                         /* DO_STATSEXT */
-   30,                         /* DO_RULE */
-   31,                         /* DO_TRIGGER */
+   29,                         /* DO_INDEX_ATTACH */
+   30,                         /* DO_STATSEXT */
+   31,                         /* DO_RULE */
+   32,                         /* DO_TRIGGER */
    27,                         /* DO_CONSTRAINT */
-   32,                         /* DO_FK_CONSTRAINT */
+   33,                         /* DO_FK_CONSTRAINT */
    2,                          /* DO_PROCLANG */
    10,                         /* DO_CAST */
    23,                         /* DO_TABLE_DATA */
    15,                         /* DO_TSCONFIG */
    16,                         /* DO_FDW */
    17,                         /* DO_FOREIGN_SERVER */
-   32,                         /* DO_DEFAULT_ACL */
+   33,                         /* DO_DEFAULT_ACL */
    3,                          /* DO_TRANSFORM */
    21,                         /* DO_BLOB */
    25,                         /* DO_BLOB_DATA */
    22,                         /* DO_PRE_DATA_BOUNDARY */
    26,                         /* DO_POST_DATA_BOUNDARY */
-   33,                         /* DO_EVENT_TRIGGER */
-   38,                         /* DO_REFRESH_MATVIEW */
-   34,                         /* DO_POLICY */
-   35,                         /* DO_PUBLICATION */
-   36,                         /* DO_PUBLICATION_REL */
-   37                          /* DO_SUBSCRIPTION */
+   34,                         /* DO_EVENT_TRIGGER */
+   39,                         /* DO_REFRESH_MATVIEW */
+   35,                         /* DO_POLICY */
+   36,                         /* DO_PUBLICATION */
+   37,                         /* DO_PUBLICATION_REL */
+   38                          /* DO_SUBSCRIPTION */
 };
 
 static DumpId preDataBoundId;
    addObjectDependency(constraintobj, postDataBoundId);
 }
 
+static void
+repairIndexLoop(DumpableObject *partedindex,
+               DumpableObject *partindex)
+{
+   removeObjectDependency(partedindex, partindex->dumpId);
+}
+
 /*
  * Fix a dependency loop, or die trying ...
  *
        return;
    }
 
+   /* index on partitioned table and corresponding index on partition */
+   if (nLoop == 2 &&
+       loop[0]->objType == DO_INDEX &&
+       loop[1]->objType == DO_INDEX)
+   {
+       if (((IndxInfo *) loop[0])->parentidx == loop[1]->catId.oid)
+       {
+           repairIndexLoop(loop[0], loop[1]);
+           return;
+       }
+       else if (((IndxInfo *) loop[1])->parentidx == loop[0]->catId.oid)
+       {
+           repairIndexLoop(loop[1], loop[0]);
+           return;
+       }
+   }
+
    /* Indirect loop involving table and attribute default */
    if (nLoop > 2)
    {
                     "INDEX %s  (ID %d OID %u)",
                     obj->name, obj->dumpId, obj->catId.oid);
            return;
+       case DO_INDEX_ATTACH:
+           snprintf(buf, bufsize,
+                    "INDEX ATTACH %s  (ID %d)",
+                    obj->name, obj->dumpId);
+           return;
        case DO_STATSEXT:
            snprintf(buf, bufsize,
                     "STATISTICS %s  (ID %d OID %u)",
 
            section_pre_data         => 1,
            test_schema_plus_blobs   => 1, }, },
 
+   'CREATE INDEX ON ONLY measurement' => {
+       all_runs     => 1,
+       catch_all    => 'CREATE ... commands',
+       create_order => 92,
+       create_sql   => 'CREATE INDEX ON dump_test.measurement (city_id, logdate);',
+       regexp => qr/^
+       \QCREATE INDEX measurement_city_id_logdate_idx ON ONLY measurement USING\E
+       /xm,
+       like => {
+           binary_upgrade           => 1,
+           clean                    => 1,
+           clean_if_exists          => 1,
+           createdb                 => 1,
+           defaults                 => 1,
+           exclude_test_table       => 1,
+           exclude_test_table_data  => 1,
+           no_blobs                 => 1,
+           no_privs                 => 1,
+           no_owner                 => 1,
+           only_dump_test_schema    => 1,
+           pg_dumpall_dbprivs       => 1,
+           schema_only              => 1,
+           section_post_data        => 1,
+           test_schema_plus_blobs   => 1,
+           with_oids                => 1, },
+       unlike => {
+           exclude_dump_test_schema => 1,
+           only_dump_test_table     => 1,
+           pg_dumpall_globals       => 1,
+           pg_dumpall_globals_clean => 1,
+           role                     => 1,
+           section_pre_data         => 1, }, },
+
+   'CREATE INDEX ... ON measurement_y2006_m2' => {
+       all_runs     => 1,
+       catch_all    => 'CREATE ... commands',
+       regexp       => qr/^
+       \QCREATE INDEX measurement_y2006m2_city_id_logdate_idx ON measurement_y2006m2 \E
+       /xm,
+       like => {
+           binary_upgrade           => 1,
+           clean                    => 1,
+           clean_if_exists          => 1,
+           createdb                 => 1,
+           defaults                 => 1,
+           exclude_dump_test_schema => 1,
+           exclude_test_table       => 1,
+           exclude_test_table_data  => 1,
+           no_blobs                 => 1,
+           no_privs                 => 1,
+           no_owner                 => 1,
+           pg_dumpall_dbprivs       => 1,
+           role                     => 1,
+           schema_only              => 1,
+           section_post_data        => 1,
+           with_oids                => 1, },
+       unlike => {
+           only_dump_test_schema    => 1,
+           only_dump_test_table     => 1,
+           pg_dumpall_globals       => 1,
+           pg_dumpall_globals_clean => 1,
+           section_pre_data         => 1,
+           test_schema_plus_blobs   => 1, }, },
+
+   'ALTER INDEX ... ATTACH PARTITION' => {
+       all_runs     => 1,
+       catch_all    => 'CREATE ... commands',
+       regexp       => qr/^
+       \QALTER INDEX dump_test.measurement_city_id_logdate_idx ATTACH PARTITION dump_test_second_schema.measurement_y2006m2_city_id_logdate_idx\E
+       /xm,
+       like => {
+           binary_upgrade           => 1,
+           clean                    => 1,
+           clean_if_exists          => 1,
+           createdb                 => 1,
+           defaults                 => 1,
+           exclude_dump_test_schema => 1,
+           exclude_test_table       => 1,
+           exclude_test_table_data  => 1,
+           no_blobs                 => 1,
+           no_privs                 => 1,
+           no_owner                 => 1,
+           pg_dumpall_dbprivs       => 1,
+           role                     => 1,
+           schema_only              => 1,
+           section_post_data        => 1,
+           with_oids                => 1, },
+       unlike => {
+           only_dump_test_schema    => 1,
+           only_dump_test_table     => 1,
+           pg_dumpall_globals       => 1,
+           pg_dumpall_globals_clean => 1,
+           section_pre_data         => 1,
+           test_schema_plus_blobs   => 1, }, },
+
    'CREATE VIEW test_view' => {
        all_runs     => 1,
        catch_all    => 'CREATE ... commands',
 
        appendPQExpBufferStr(&buf, ",\n  a.attidentity");
    else
        appendPQExpBufferStr(&buf, ",\n  ''::pg_catalog.char AS attidentity");
-   if (tableinfo.relkind == RELKIND_INDEX)
+   if (tableinfo.relkind == RELKIND_INDEX ||
+       tableinfo.relkind == RELKIND_PARTITIONED_INDEX)
        appendPQExpBufferStr(&buf, ",\n  pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE) AS indexdef");
    else
        appendPQExpBufferStr(&buf, ",\n  NULL AS indexdef");
                                  schemaname, relationname);
            break;
        case RELKIND_INDEX:
+       case RELKIND_PARTITIONED_INDEX:
            if (tableinfo.relpersistence == 'u')
                printfPQExpBuffer(&title, _("Unlogged index \"%s.%s\""),
                                  schemaname, relationname);
        show_column_details = true;
    }
 
-   if (tableinfo.relkind == RELKIND_INDEX)
+   if (tableinfo.relkind == RELKIND_INDEX ||
+       tableinfo.relkind == RELKIND_PARTITIONED_INDEX)
        headers[cols++] = gettext_noop("Definition");
 
    if (tableinfo.relkind == RELKIND_FOREIGN_TABLE && pset.sversion >= 90200)
        headers[cols++] = gettext_noop("Storage");
        if (tableinfo.relkind == RELKIND_RELATION ||
            tableinfo.relkind == RELKIND_INDEX ||
+           tableinfo.relkind == RELKIND_PARTITIONED_INDEX ||
            tableinfo.relkind == RELKIND_MATVIEW ||
            tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
            tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
        }
 
        /* Expression for index column */
-       if (tableinfo.relkind == RELKIND_INDEX)
+       if (tableinfo.relkind == RELKIND_INDEX ||
+           tableinfo.relkind == RELKIND_PARTITIONED_INDEX)
            printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
 
        /* FDW options for foreign table column, only for 9.2 or later */
            /* Statistics target, if the relkind supports this feature */
            if (tableinfo.relkind == RELKIND_RELATION ||
                tableinfo.relkind == RELKIND_INDEX ||
+               tableinfo.relkind == RELKIND_PARTITIONED_INDEX ||
                tableinfo.relkind == RELKIND_MATVIEW ||
                tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
                tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
        PQclear(result);
    }
 
-   if (tableinfo.relkind == RELKIND_INDEX)
+   if (tableinfo.relkind == RELKIND_INDEX ||
+       tableinfo.relkind == RELKIND_PARTITIONED_INDEX)
    {
        /* Footer information about an index */
        PGresult   *result;
                      " WHEN 's' THEN '%s'"
                      " WHEN " CppAsString2(RELKIND_FOREIGN_TABLE) " THEN '%s'"
                      " WHEN " CppAsString2(RELKIND_PARTITIONED_TABLE) " THEN '%s'"
+                     " WHEN " CppAsString2(RELKIND_PARTITIONED_INDEX) " THEN '%s'"
                      " END as \"%s\",\n"
                      "  pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
                      gettext_noop("Schema"),
                      gettext_noop("special"),
                      gettext_noop("foreign table"),
                      gettext_noop("table"),    /* partitioned table */
+                     gettext_noop("index"),    /* partitioned index */
                      gettext_noop("Type"),
                      gettext_noop("Owner"));
 
    if (showMatViews)
        appendPQExpBufferStr(&buf, CppAsString2(RELKIND_MATVIEW) ",");
    if (showIndexes)
-       appendPQExpBufferStr(&buf, CppAsString2(RELKIND_INDEX) ",");
+       appendPQExpBufferStr(&buf, CppAsString2(RELKIND_INDEX) ","
+                            CppAsString2(RELKIND_PARTITIONED_INDEX) ",");
    if (showSeq)
        appendPQExpBufferStr(&buf, CppAsString2(RELKIND_SEQUENCE) ",");
    if (showSystem || pattern)
 
    /* catname */
    "pg_catalog.pg_class c",
    /* selcondition */
-   "c.relkind IN (" CppAsString2(RELKIND_INDEX) ")",
+   "c.relkind IN (" CppAsString2(RELKIND_INDEX) ", "
+   CppAsString2(RELKIND_PARTITIONED_INDEX) ")",
    /* viscondition */
    "pg_catalog.pg_table_is_visible(c.oid)",
    /* namespace */
    NULL
 };
 
+static const SchemaQuery Query_for_list_of_tpm = {
+   /* catname */
+   "pg_catalog.pg_class c",
+   /* selcondition */
+   "c.relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+   CppAsString2(RELKIND_PARTITIONED_TABLE) ", "
+   CppAsString2(RELKIND_MATVIEW) ")",
+   /* viscondition */
+   "pg_catalog.pg_table_is_visible(c.oid)",
+   /* namespace */
+   "c.relnamespace",
+   /* result */
+   "pg_catalog.quote_ident(c.relname)",
+   /* qualresult */
+   NULL
+};
+
 static const SchemaQuery Query_for_list_of_tm = {
    /* catname */
    "pg_catalog.pg_class c",
                                   "UNION SELECT 'ALL IN TABLESPACE'");
    /* ALTER INDEX <name> */
    else if (Matches3("ALTER", "INDEX", MatchAny))
-       COMPLETE_WITH_LIST5("ALTER COLUMN", "OWNER TO", "RENAME TO", "SET", "RESET");
+       COMPLETE_WITH_LIST6("ALTER COLUMN", "OWNER TO", "RENAME TO", "SET",
+                           "RESET", "ATTACH PARTITION");
+   else if (Matches4("ALTER", "INDEX", MatchAny, "ATTACH"))
+       COMPLETE_WITH_CONST("PARTITION");
+   else if (Matches5("ALTER", "INDEX", MatchAny, "ATTACH", "PARTITION"))
+       COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes, NULL);
    /* ALTER INDEX <name> ALTER COLUMN <colnum> */
    else if (Matches6("ALTER", "INDEX", MatchAny, "ALTER", "COLUMN", MatchAny))
        COMPLETE_WITH_CONST("SET STATISTICS");
        COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_indexes,
                                   " UNION SELECT 'ON'"
                                   " UNION SELECT 'CONCURRENTLY'");
-   /* Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of tables  */
+   /*
+    * Complete ... INDEX|CONCURRENTLY [<name>] ON with a list of relations
+    * that can indexes can be created on
+    */
    else if (TailMatches3("INDEX|CONCURRENTLY", MatchAny, "ON") ||
             TailMatches2("INDEX|CONCURRENTLY", "ON"))
-       COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tm, NULL);
+       COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tpm, NULL);
 
    /*
     * Complete CREATE|UNIQUE INDEX CONCURRENTLY with "ON" and existing
 
  * Example: a trigger that's created to enforce a foreign-key constraint
  * is made internally dependent on the constraint's pg_constraint entry.
  *
+ * DEPENDENCY_INTERNAL_AUTO ('I'): the dependent object was created as
+ * part of creation of the referenced object, and is really just a part
+ * of its internal implementation.  A DROP of the dependent object will
+ * be disallowed outright (we'll tell the user to issue a DROP against the
+ * referenced object, instead).  While a regular internal dependency will
+ * prevent the dependent object from being dropped while any such
+ * dependencies remain, DEPENDENCY_INTERNAL_AUTO will allow such a drop as
+ * long as the object can be found by following any of such dependencies.
+ * Example: an index on a partition is made internal-auto-dependent on
+ * both the partition itself as well as on the index on the parent
+ * partitioned table; so the partition index is dropped together with
+ * either the partition it indexes, or with the parent index it is attached
+ * to.
+
  * DEPENDENCY_EXTENSION ('e'): the dependent object is a member of the
  * extension that is the referenced object.  The dependent object can be
  * dropped only via DROP EXTENSION on the referenced object.  Functionally
    DEPENDENCY_NORMAL = 'n',
    DEPENDENCY_AUTO = 'a',
    DEPENDENCY_INTERNAL = 'i',
+   DEPENDENCY_INTERNAL_AUTO = 'I',
    DEPENDENCY_EXTENSION = 'e',
    DEPENDENCY_AUTO_EXTENSION = 'x',
    DEPENDENCY_PIN = 'p'
 
 #define    INDEX_CREATE_SKIP_BUILD             (1 << 2)
 #define    INDEX_CREATE_CONCURRENT             (1 << 3)
 #define    INDEX_CREATE_IF_NOT_EXISTS          (1 << 4)
+#define    INDEX_CREATE_PARTITIONED            (1 << 5)
+#define INDEX_CREATE_INVALID               (1 << 6)
 
 extern Oid index_create(Relation heapRelation,
             const char *indexRelationName,
             Oid indexRelationId,
+            Oid parentIndexRelid,
             Oid relFileNode,
             IndexInfo *indexInfo,
             List *indexColNames,
 
 extern IndexInfo *BuildIndexInfo(Relation index);
 
+extern bool CompareIndexInfo(IndexInfo *info1, IndexInfo *info2,
+                Oid *collations1, Oid *collations2,
+                Oid *opfamilies1, Oid *opfamilies2,
+                AttrNumber *attmap, int maplen);
+
 extern void BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii);
 
 extern void FormIndexDatum(IndexInfo *indexInfo,
 extern void SerializeReindexState(Size maxsize, char *start_address);
 extern void RestoreReindexState(void *reindexstate);
 
+extern void IndexSetParentIndex(Relation idx, Oid parentOid);
+
 #endif                         /* INDEX_H */
 
 #define          RELKIND_COMPOSITE_TYPE  'c'   /* composite type */
 #define          RELKIND_FOREIGN_TABLE   'f'   /* foreign table */
 #define          RELKIND_PARTITIONED_TABLE 'p' /* partitioned table */
+#define          RELKIND_PARTITIONED_INDEX 'I' /* partitioned index */
 
 #define          RELPERSISTENCE_PERMANENT  'p' /* regular table */
 #define          RELPERSISTENCE_UNLOGGED   'u' /* unlogged permanent table */
 
 extern bool has_subclass(Oid relationId);
 extern bool has_superclass(Oid relationId);
 extern bool typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId);
+extern void StoreSingleInheritance(Oid relationId, Oid parentOid,
+                      int32 seqNumber);
+extern bool DeleteInheritsTuple(Oid inhrelid, Oid inhparent);
 
 #endif                         /* PG_INHERITS_FN_H */
 
 extern ObjectAddress DefineIndex(Oid relationId,
            IndexStmt *stmt,
            Oid indexRelationId,
+           Oid parentIndexId,
            bool is_alter_table,
            bool check_rights,
            bool check_not_in_use,
            bool skip_build,
            bool quiet);
-extern Oid ReindexIndex(RangeVar *indexRelation, int options);
+extern void ReindexIndex(RangeVar *indexRelation, int options);
 extern Oid ReindexTable(RangeVar *relation, int options);
 extern void ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
                      int options);
 
    bool        ii_ReadyForInserts;
    bool        ii_Concurrent;
    bool        ii_BrokenHotChain;
+   Oid         ii_Am;
    void       *ii_AmCache;
    MemoryContext ii_Context;
 } IndexInfo;
 
 } PartitionRangeDatum;
 
 /*
- * PartitionCmd - info for ALTER TABLE ATTACH/DETACH PARTITION commands
+ * PartitionCmd - info for ALTER TABLE/INDEX ATTACH/DETACH PARTITION commands
  */
 typedef struct PartitionCmd
 {
  * index, just a UNIQUE/PKEY constraint using an existing index.  isconstraint
  * must always be true in this case, and the fields describing the index
  * properties are empty.
+ *
+ * The relation to build the index on can be represented either by name
+ * (in which case the RangeVar indicates whether to recurse or not) or by OID
+ * (in which case the command is always recursive).
  * ----------------------
  */
 typedef struct IndexStmt
    NodeTag     type;
    char       *idxname;        /* name of new index, or NULL for default */
    RangeVar   *relation;       /* relation to build index on */
+   Oid         relationId;     /* OID of relation to build index on */
    char       *accessMethod;   /* name of access method (eg. btree) */
    char       *tableSpace;     /* tablespace, or NULL for default */
    List       *indexParams;    /* columns to index: a list of IndexElem */
 
 extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt);
 extern PartitionBoundSpec *transformPartitionBound(ParseState *pstate, Relation parent,
                        PartitionBoundSpec *spec);
+extern IndexStmt *generateClonedIndexStmt(RangeVar *heapRel, Oid heapOid,
+                       Relation source_idx,
+                       const AttrNumber *attmap, int attmap_length);
 
 #endif                         /* PARSE_UTILCMD_H */
 
 create table tab2 (x int, y tab1);
 alter table tab1 alter column b type varchar; -- fails
 ERROR:  cannot alter table "tab1" because column "tab2.y" uses its row type
+-- Alter column type that's part of a partitioned index
+create table at_partitioned (a int, b text) partition by range (a);
+create table at_part_1 partition of at_partitioned for values from (0) to (1000);
+insert into at_partitioned values (512, '0.123');
+create table at_part_2 (b text, a int);
+insert into at_part_2 values ('1.234', 1024);
+create index on at_partitioned (b);
+create index on at_partitioned (a);
+\d at_part_1
+             Table "public.at_part_1"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | text    |           |          | 
+Partition of: at_partitioned FOR VALUES FROM (0) TO (1000)
+Indexes:
+    "at_part_1_a_idx" btree (a)
+    "at_part_1_b_idx" btree (b)
+
+\d at_part_2
+             Table "public.at_part_2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ b      | text    |           |          | 
+ a      | integer |           |          | 
+
+alter table at_partitioned attach partition at_part_2 for values from (1000) to (2000);
+\d at_part_2
+             Table "public.at_part_2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ b      | text    |           |          | 
+ a      | integer |           |          | 
+Partition of: at_partitioned FOR VALUES FROM (1000) TO (2000)
+Indexes:
+    "at_part_2_a_idx" btree (a)
+    "at_part_2_b_idx" btree (b)
+
+alter table at_partitioned alter column b type numeric using b::numeric;
+\d at_part_1
+             Table "public.at_part_1"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | numeric |           |          | 
+Partition of: at_partitioned FOR VALUES FROM (0) TO (1000)
+Indexes:
+    "at_part_1_a_idx" btree (a)
+    "at_part_1_b_idx" btree (b)
+
+\d at_part_2
+             Table "public.at_part_2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ b      | numeric |           |          | 
+ a      | integer |           |          | 
+Partition of: at_partitioned FOR VALUES FROM (1000) TO (2000)
+Indexes:
+    "at_part_2_a_idx" btree (a)
+    "at_part_2_b_idx" btree (b)
+
 -- disallow recursive containment of row types
 create temp table recur1 (f1 int);
 alter table recur1 add column f2 recur1; -- fails
 );
 CREATE TABLE fail_part (like unparted);
 ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
-ERROR:  "unparted" is not partitioned
+ERROR:  table "unparted" is not partitioned
 DROP TABLE unparted, fail_part;
 -- check that partition bound is compatible
 CREATE TABLE list_parted (
 -- check that the table is partitioned at all
 CREATE TABLE regular_table (a int);
 ALTER TABLE regular_table DETACH PARTITION any_name;
-ERROR:  "regular_table" is not partitioned
+ERROR:  table "regular_table" is not partitioned
 DROP TABLE regular_table;
 -- check that the partition being detached exists at all
 ALTER TABLE list_parted2 DETACH PARTITION part_4;
 
--- /dev/null
+-- Creating an index on a partitioned table makes the partitions
+-- automatically get the index
+create table idxpart (a int, b int, c text) partition by range (a);
+create table idxpart1 partition of idxpart for values from (0) to (10);
+create table idxpart2 partition of idxpart for values from (10) to (100)
+   partition by range (b);
+create table idxpart21 partition of idxpart2 for values from (0) to (100);
+create index on idxpart (a);
+select relname, relkind, inhparent::regclass
+    from pg_class left join pg_index ix on (indexrelid = oid)
+   left join pg_inherits on (ix.indexrelid = inhrelid)
+   where relname like 'idxpart%' order by relname;
+     relname     | relkind |   inhparent    
+-----------------+---------+----------------
+ idxpart         | p       | 
+ idxpart1        | r       | 
+ idxpart1_a_idx  | i       | idxpart_a_idx
+ idxpart2        | p       | 
+ idxpart21       | r       | 
+ idxpart21_a_idx | i       | idxpart2_a_idx
+ idxpart2_a_idx  | I       | idxpart_a_idx
+ idxpart_a_idx   | I       | 
+(8 rows)
+
+drop table idxpart;
+-- Some unsupported features
+create table idxpart (a int, b int, c text) partition by range (a);
+create table idxpart1 partition of idxpart for values from (0) to (10);
+create unique index on idxpart (a);
+ERROR:  cannot create unique index on partitioned table "idxpart"
+create index concurrently on idxpart (a);
+ERROR:  cannot create index on partitioned table "idxpart" concurrently
+drop table idxpart;
+-- If a table without index is attached as partition to a table with
+-- an index, the index is automatically created
+create table idxpart (a int, b int, c text) partition by range (a);
+create index idxparti on idxpart (a);
+create index idxparti2 on idxpart (b, c);
+create table idxpart1 (like idxpart);
+\d idxpart1
+              Table "public.idxpart1"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+ c      | text    |           |          | 
+
+alter table idxpart attach partition idxpart1 for values from (0) to (10);
+\d idxpart1
+              Table "public.idxpart1"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+ c      | text    |           |          | 
+Partition of: idxpart FOR VALUES FROM (0) TO (10)
+Indexes:
+    "idxpart1_a_idx" btree (a)
+    "idxpart1_b_c_idx" btree (b, c)
+
+drop table idxpart;
+-- If a partition already has an index, don't create a duplicative one
+create table idxpart (a int, b int) partition by range (a, b);
+create table idxpart1 partition of idxpart for values from (0, 0) to (10, 10);
+create index on idxpart1 (a, b);
+create index on idxpart (a, b);
+\d idxpart1
+              Table "public.idxpart1"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+Partition of: idxpart FOR VALUES FROM (0, 0) TO (10, 10)
+Indexes:
+    "idxpart1_a_b_idx" btree (a, b)
+
+select relname, relkind, inhparent::regclass
+    from pg_class left join pg_index ix on (indexrelid = oid)
+   left join pg_inherits on (ix.indexrelid = inhrelid)
+   where relname like 'idxpart%' order by relname;
+     relname      | relkind |    inhparent    
+------------------+---------+-----------------
+ idxpart          | p       | 
+ idxpart1         | r       | 
+ idxpart1_a_b_idx | i       | idxpart_a_b_idx
+ idxpart_a_b_idx  | I       | 
+(4 rows)
+
+drop table idxpart;
+-- DROP behavior for partitioned indexes
+create table idxpart (a int) partition by range (a);
+create index on idxpart (a);
+create table idxpart1 partition of idxpart for values from (0) to (10);
+drop index idxpart1_a_idx; -- no way
+ERROR:  cannot drop index idxpart1_a_idx because index idxpart_a_idx requires it
+HINT:  You can drop index idxpart_a_idx instead.
+drop index idxpart_a_idx;  -- both indexes go away
+select relname, relkind from pg_class
+  where relname like 'idxpart%' order by relname;
+ relname  | relkind 
+----------+---------
+ idxpart  | p
+ idxpart1 | r
+(2 rows)
+
+create index on idxpart (a);
+drop table idxpart1;       -- the index on partition goes away too
+select relname, relkind from pg_class
+  where relname like 'idxpart%' order by relname;
+    relname    | relkind 
+---------------+---------
+ idxpart       | p
+ idxpart_a_idx | I
+(2 rows)
+
+drop table idxpart;
+-- ALTER INDEX .. ATTACH, error cases
+create table idxpart (a int, b int) partition by range (a, b);
+create table idxpart1 partition of idxpart for values from (0, 0) to (10, 10);
+create index idxpart_a_b_idx on only idxpart (a, b);
+create index idxpart1_a_b_idx on idxpart1 (a, b);
+create index idxpart1_tst1 on idxpart1 (b, a);
+create index idxpart1_tst2 on idxpart1 using hash (a);
+create index idxpart1_tst3 on idxpart1 (a, b) where a > 10;
+alter index idxpart attach partition idxpart1;
+ERROR:  "idxpart" is not an index
+alter index idxpart_a_b_idx attach partition idxpart1;
+ERROR:  "idxpart1" is not an index
+alter index idxpart_a_b_idx attach partition idxpart_a_b_idx;
+ERROR:  cannot attach index "idxpart_a_b_idx" as a partition of index "idxpart_a_b_idx"
+DETAIL:  Index "idxpart_a_b_idx" is not an index on any partition of table "idxpart".
+alter index idxpart_a_b_idx attach partition idxpart1_b_idx;
+ERROR:  relation "idxpart1_b_idx" does not exist
+alter index idxpart_a_b_idx attach partition idxpart1_tst1;
+ERROR:  cannot attach index "idxpart1_tst1" as a partition of index "idxpart_a_b_idx"
+DETAIL:  The index definitions do not match.
+alter index idxpart_a_b_idx attach partition idxpart1_tst2;
+ERROR:  cannot attach index "idxpart1_tst2" as a partition of index "idxpart_a_b_idx"
+DETAIL:  The index definitions do not match.
+alter index idxpart_a_b_idx attach partition idxpart1_tst3;
+ERROR:  cannot attach index "idxpart1_tst3" as a partition of index "idxpart_a_b_idx"
+DETAIL:  The index definitions do not match.
+-- OK
+alter index idxpart_a_b_idx attach partition idxpart1_a_b_idx;
+alter index idxpart_a_b_idx attach partition idxpart1_a_b_idx; -- quiet
+-- reject dupe
+create index idxpart1_2_a_b on idxpart1 (a, b);
+alter index idxpart_a_b_idx attach partition idxpart1_2_a_b;
+ERROR:  cannot attach index "idxpart1_2_a_b" as a partition of index "idxpart_a_b_idx"
+DETAIL:  Another index is already attached for partition "idxpart1".
+drop table idxpart;
+-- make sure everything's gone
+select indexrelid::regclass, indrelid::regclass
+  from pg_index where indexrelid::regclass::text like 'idxpart%';
+ indexrelid | indrelid 
+------------+----------
+(0 rows)
+
+-- Don't auto-attach incompatible indexes
+create table idxpart (a int, b int) partition by range (a);
+create table idxpart1 (a int, b int);
+create index on idxpart1 using hash (a);
+create index on idxpart1 (a) where b > 1;
+create index on idxpart1 ((a + 0));
+create index on idxpart1 (a, a);
+create index on idxpart (a);
+alter table idxpart attach partition idxpart1 for values from (0) to (1000);
+\d idxpart1
+              Table "public.idxpart1"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+Partition of: idxpart FOR VALUES FROM (0) TO (1000)
+Indexes:
+    "idxpart1_a_a1_idx" btree (a, a)
+    "idxpart1_a_idx" hash (a)
+    "idxpart1_a_idx1" btree (a) WHERE b > 1
+    "idxpart1_a_idx2" btree (a)
+    "idxpart1_expr_idx" btree ((a + 0))
+
+drop table idxpart;
+-- If CREATE INDEX ONLY, don't create indexes on partitions; and existing
+-- indexes on partitions don't change parent.  ALTER INDEX ATTACH can change
+-- the parent after the fact.
+create table idxpart (a int) partition by range (a);
+create table idxpart1 partition of idxpart for values from (0) to (100);
+create table idxpart2 partition of idxpart for values from (100) to (1000)
+  partition by range (a);
+create table idxpart21 partition of idxpart2 for values from (100) to (200);
+create table idxpart22 partition of idxpart2 for values from (200) to (300);
+create index on idxpart22 (a);
+create index on only idxpart2 (a);
+create index on idxpart (a);
+-- Here we expect that idxpart1 and idxpart2 have a new index, but idxpart21
+-- does not; also, idxpart22 is not attached.
+\d idxpart1
+              Table "public.idxpart1"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+Partition of: idxpart FOR VALUES FROM (0) TO (100)
+Indexes:
+    "idxpart1_a_idx" btree (a)
+
+\d idxpart2
+              Table "public.idxpart2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+Partition of: idxpart FOR VALUES FROM (100) TO (1000)
+Partition key: RANGE (a)
+Indexes:
+    "idxpart2_a_idx" btree (a) INVALID
+Number of partitions: 2 (Use \d+ to list them.)
+
+\d idxpart21
+             Table "public.idxpart21"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+Partition of: idxpart2 FOR VALUES FROM (100) TO (200)
+
+select indexrelid::regclass, indrelid::regclass, inhparent::regclass
+  from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid)
+where indexrelid::regclass::text like 'idxpart%'
+  order by indrelid::regclass::text collate "C";
+   indexrelid    | indrelid  |   inhparent   
+-----------------+-----------+---------------
+ idxpart_a_idx   | idxpart   | 
+ idxpart1_a_idx  | idxpart1  | idxpart_a_idx
+ idxpart2_a_idx  | idxpart2  | idxpart_a_idx
+ idxpart22_a_idx | idxpart22 | 
+(4 rows)
+
+alter index idxpart2_a_idx attach partition idxpart22_a_idx;
+select indexrelid::regclass, indrelid::regclass, inhparent::regclass
+  from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid)
+where indexrelid::regclass::text like 'idxpart%'
+  order by indrelid::regclass::text collate "C";
+   indexrelid    | indrelid  |   inhparent    
+-----------------+-----------+----------------
+ idxpart_a_idx   | idxpart   | 
+ idxpart1_a_idx  | idxpart1  | idxpart_a_idx
+ idxpart2_a_idx  | idxpart2  | idxpart_a_idx
+ idxpart22_a_idx | idxpart22 | idxpart2_a_idx
+(4 rows)
+
+-- attaching idxpart22 is not enough to set idxpart22_a_idx valid ...
+alter index idxpart2_a_idx attach partition idxpart22_a_idx;
+\d idxpart2
+              Table "public.idxpart2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+Partition of: idxpart FOR VALUES FROM (100) TO (1000)
+Partition key: RANGE (a)
+Indexes:
+    "idxpart2_a_idx" btree (a) INVALID
+Number of partitions: 2 (Use \d+ to list them.)
+
+-- ... but this one is.
+create index on idxpart21 (a);
+alter index idxpart2_a_idx attach partition idxpart21_a_idx;
+\d idxpart2
+              Table "public.idxpart2"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+Partition of: idxpart FOR VALUES FROM (100) TO (1000)
+Partition key: RANGE (a)
+Indexes:
+    "idxpart2_a_idx" btree (a)
+Number of partitions: 2 (Use \d+ to list them.)
+
+drop table idxpart;
+-- When a table is attached a partition and it already has an index, a
+-- duplicate index should not get created, but rather the index becomes
+-- attached to the parent's index.
+create table idxpart (a int, b int, c text) partition by range (a);
+create index idxparti on idxpart (a);
+create index idxparti2 on idxpart (b, c);
+create table idxpart1 (like idxpart including indexes);
+\d idxpart1
+              Table "public.idxpart1"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+ c      | text    |           |          | 
+Indexes:
+    "idxpart1_a_idx" btree (a)
+    "idxpart1_b_c_idx" btree (b, c)
+
+select relname, relkind, inhparent::regclass
+    from pg_class left join pg_index ix on (indexrelid = oid)
+   left join pg_inherits on (ix.indexrelid = inhrelid)
+   where relname like 'idxpart%' order by relname;
+     relname      | relkind | inhparent 
+------------------+---------+-----------
+ idxpart          | p       | 
+ idxpart1         | r       | 
+ idxpart1_a_idx   | i       | 
+ idxpart1_b_c_idx | i       | 
+ idxparti         | I       | 
+ idxparti2        | I       | 
+(6 rows)
+
+alter table idxpart attach partition idxpart1 for values from (0) to (10);
+\d idxpart1
+              Table "public.idxpart1"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+ c      | text    |           |          | 
+Partition of: idxpart FOR VALUES FROM (0) TO (10)
+Indexes:
+    "idxpart1_a_idx" btree (a)
+    "idxpart1_b_c_idx" btree (b, c)
+
+select relname, relkind, inhparent::regclass
+    from pg_class left join pg_index ix on (indexrelid = oid)
+   left join pg_inherits on (ix.indexrelid = inhrelid)
+   where relname like 'idxpart%' order by relname;
+     relname      | relkind | inhparent 
+------------------+---------+-----------
+ idxpart          | p       | 
+ idxpart1         | r       | 
+ idxpart1_a_idx   | i       | idxparti
+ idxpart1_b_c_idx | i       | idxparti2
+ idxparti         | I       | 
+ idxparti2        | I       | 
+(6 rows)
+
+drop table idxpart;
+-- Verify that attaching an invalid index does not mark the parent index valid.
+-- On the other hand, attaching a valid index marks not only its direct
+-- ancestor valid, but also any indirect ancestor that was only missing the one
+-- that was just made valid
+create table idxpart (a int, b int) partition by range (a);
+create table idxpart1 partition of idxpart for values from (1) to (1000) partition by range (a);
+create table idxpart11 partition of idxpart1 for values from (1) to (100);
+create index on only idxpart1 (a);
+create index on only idxpart (a);
+-- this results in two invalid indexes:
+select relname, indisvalid from pg_class join pg_index on indexrelid = oid
+   where relname like 'idxpart%' order by relname;
+    relname     | indisvalid 
+----------------+------------
+ idxpart1_a_idx | f
+ idxpart_a_idx  | f
+(2 rows)
+
+-- idxpart1_a_idx is not valid, so idxpart_a_idx should not become valid:
+alter index idxpart_a_idx attach partition idxpart1_a_idx;
+select relname, indisvalid from pg_class join pg_index on indexrelid = oid
+   where relname like 'idxpart%' order by relname;
+    relname     | indisvalid 
+----------------+------------
+ idxpart1_a_idx | f
+ idxpart_a_idx  | f
+(2 rows)
+
+-- after creating and attaching this, both idxpart1_a_idx and idxpart_a_idx
+-- should become valid
+create index on idxpart11 (a);
+alter index idxpart1_a_idx attach partition idxpart11_a_idx;
+select relname, indisvalid from pg_class join pg_index on indexrelid = oid
+   where relname like 'idxpart%' order by relname;
+     relname     | indisvalid 
+-----------------+------------
+ idxpart11_a_idx | t
+ idxpart1_a_idx  | t
+ idxpart_a_idx   | t
+(3 rows)
+
+drop table idxpart;
+-- verify dependency handling during ALTER TABLE DETACH PARTITION
+create table idxpart (a int) partition by range (a);
+create table idxpart1 (like idxpart);
+create index on idxpart1 (a);
+create index on idxpart (a);
+create table idxpart2 (like idxpart);
+alter table idxpart attach partition idxpart1 for values from (0000) to (1000);
+alter table idxpart attach partition idxpart2 for values from (1000) to (2000);
+create table idxpart3 partition of idxpart for values from (2000) to (3000);
+select relname, relkind from pg_class where relname like 'idxpart%' order by relname;
+    relname     | relkind 
+----------------+---------
+ idxpart        | p
+ idxpart1       | r
+ idxpart1_a_idx | i
+ idxpart2       | r
+ idxpart2_a_idx | i
+ idxpart3       | r
+ idxpart3_a_idx | i
+ idxpart_a_idx  | I
+(8 rows)
+
+-- a) after detaching partitions, the indexes can be dropped independently
+alter table idxpart detach partition idxpart1;
+alter table idxpart detach partition idxpart2;
+alter table idxpart detach partition idxpart3;
+drop index idxpart1_a_idx;
+drop index idxpart2_a_idx;
+drop index idxpart3_a_idx;
+select relname, relkind from pg_class where relname like 'idxpart%' order by relname;
+    relname    | relkind 
+---------------+---------
+ idxpart       | p
+ idxpart1      | r
+ idxpart2      | r
+ idxpart3      | r
+ idxpart_a_idx | I
+(5 rows)
+
+drop table idxpart, idxpart1, idxpart2, idxpart3;
+select relname, relkind from pg_class where relname like 'idxpart%' order by relname;
+ relname | relkind 
+---------+---------
+(0 rows)
+
+create table idxpart (a int) partition by range (a);
+create table idxpart1 (like idxpart);
+create index on idxpart1 (a);
+create index on idxpart (a);
+create table idxpart2 (like idxpart);
+alter table idxpart attach partition idxpart1 for values from (0000) to (1000);
+alter table idxpart attach partition idxpart2 for values from (1000) to (2000);
+create table idxpart3 partition of idxpart for values from (2000) to (3000);
+-- b) after detaching, dropping the index on parent does not remove the others
+select relname, relkind from pg_class where relname like 'idxpart%' order by relname;
+    relname     | relkind 
+----------------+---------
+ idxpart        | p
+ idxpart1       | r
+ idxpart1_a_idx | i
+ idxpart2       | r
+ idxpart2_a_idx | i
+ idxpart3       | r
+ idxpart3_a_idx | i
+ idxpart_a_idx  | I
+(8 rows)
+
+alter table idxpart detach partition idxpart1;
+alter table idxpart detach partition idxpart2;
+alter table idxpart detach partition idxpart3;
+drop index idxpart_a_idx;
+select relname, relkind from pg_class where relname like 'idxpart%' order by relname;
+    relname     | relkind 
+----------------+---------
+ idxpart        | p
+ idxpart1       | r
+ idxpart1_a_idx | i
+ idxpart2       | r
+ idxpart2_a_idx | i
+ idxpart3       | r
+ idxpart3_a_idx | i
+(7 rows)
+
+drop table idxpart, idxpart1, idxpart2, idxpart3;
+select relname, relkind from pg_class where relname like 'idxpart%' order by relname;
+ relname | relkind 
+---------+---------
+(0 rows)
+
+-- Verify that expression indexes inherit correctly
+create table idxpart (a int, b int) partition by range (a);
+create table idxpart1 (like idxpart);
+create index on idxpart1 ((a + b));
+create index on idxpart ((a + b));
+create table idxpart2 (like idxpart);
+alter table idxpart attach partition idxpart1 for values from (0000) to (1000);
+alter table idxpart attach partition idxpart2 for values from (1000) to (2000);
+create table idxpart3 partition of idxpart for values from (2000) to (3000);
+select relname as child, inhparent::regclass as parent, pg_get_indexdef as childdef
+  from pg_class join pg_inherits on inhrelid = oid,
+  lateral pg_get_indexdef(pg_class.oid)
+  where relkind in ('i', 'I') and relname like 'idxpart%' order by relname;
+       child       |      parent      |                              childdef                              
+-------------------+------------------+--------------------------------------------------------------------
+ idxpart1_expr_idx | idxpart_expr_idx | CREATE INDEX idxpart1_expr_idx ON idxpart1 USING btree (((a + b)))
+ idxpart2_expr_idx | idxpart_expr_idx | CREATE INDEX idxpart2_expr_idx ON idxpart2 USING btree (((a + b)))
+ idxpart3_expr_idx | idxpart_expr_idx | CREATE INDEX idxpart3_expr_idx ON idxpart3 USING btree (((a + b)))
+(3 rows)
+
+drop table idxpart;
+-- Verify behavior for collation (mis)matches
+create table idxpart (a text) partition by range (a);
+create table idxpart1 (like idxpart);
+create table idxpart2 (like idxpart);
+create index on idxpart2 (a collate "POSIX");
+create index on idxpart2 (a);
+create index on idxpart2 (a collate "C");
+alter table idxpart attach partition idxpart1 for values from ('aaa') to ('bbb');
+alter table idxpart attach partition idxpart2 for values from ('bbb') to ('ccc');
+create table idxpart3 partition of idxpart for values from ('ccc') to ('ddd');
+create index on idxpart (a collate "C");
+create table idxpart4 partition of idxpart for values from ('ddd') to ('eee');
+select relname as child, inhparent::regclass as parent, pg_get_indexdef as childdef
+  from pg_class left join pg_inherits on inhrelid = oid,
+  lateral pg_get_indexdef(pg_class.oid)
+  where relkind in ('i', 'I') and relname like 'idxpart%' order by relname;
+      child      |    parent     |                                childdef                                 
+-----------------+---------------+-------------------------------------------------------------------------
+ idxpart1_a_idx  | idxpart_a_idx | CREATE INDEX idxpart1_a_idx ON idxpart1 USING btree (a COLLATE "C")
+ idxpart2_a_idx  |               | CREATE INDEX idxpart2_a_idx ON idxpart2 USING btree (a COLLATE "POSIX")
+ idxpart2_a_idx1 |               | CREATE INDEX idxpart2_a_idx1 ON idxpart2 USING btree (a)
+ idxpart2_a_idx2 | idxpart_a_idx | CREATE INDEX idxpart2_a_idx2 ON idxpart2 USING btree (a COLLATE "C")
+ idxpart3_a_idx  | idxpart_a_idx | CREATE INDEX idxpart3_a_idx ON idxpart3 USING btree (a COLLATE "C")
+ idxpart4_a_idx  | idxpart_a_idx | CREATE INDEX idxpart4_a_idx ON idxpart4 USING btree (a COLLATE "C")
+ idxpart_a_idx   |               | CREATE INDEX idxpart_a_idx ON ONLY idxpart USING btree (a COLLATE "C")
+(7 rows)
+
+drop table idxpart;
+-- Verify behavior for opclass (mis)matches
+create table idxpart (a text) partition by range (a);
+create table idxpart1 (like idxpart);
+create table idxpart2 (like idxpart);
+create index on idxpart2 (a);
+alter table idxpart attach partition idxpart1 for values from ('aaa') to ('bbb');
+alter table idxpart attach partition idxpart2 for values from ('bbb') to ('ccc');
+create table idxpart3 partition of idxpart for values from ('ccc') to ('ddd');
+create index on idxpart (a text_pattern_ops);
+create table idxpart4 partition of idxpart for values from ('ddd') to ('eee');
+-- must *not* have attached the index we created on idxpart2
+select relname as child, inhparent::regclass as parent, pg_get_indexdef as childdef
+  from pg_class left join pg_inherits on inhrelid = oid,
+  lateral pg_get_indexdef(pg_class.oid)
+  where relkind in ('i', 'I') and relname like 'idxpart%' order by relname;
+      child      |    parent     |                                  childdef                                   
+-----------------+---------------+-----------------------------------------------------------------------------
+ idxpart1_a_idx  | idxpart_a_idx | CREATE INDEX idxpart1_a_idx ON idxpart1 USING btree (a text_pattern_ops)
+ idxpart2_a_idx  |               | CREATE INDEX idxpart2_a_idx ON idxpart2 USING btree (a)
+ idxpart2_a_idx1 | idxpart_a_idx | CREATE INDEX idxpart2_a_idx1 ON idxpart2 USING btree (a text_pattern_ops)
+ idxpart3_a_idx  | idxpart_a_idx | CREATE INDEX idxpart3_a_idx ON idxpart3 USING btree (a text_pattern_ops)
+ idxpart4_a_idx  | idxpart_a_idx | CREATE INDEX idxpart4_a_idx ON idxpart4 USING btree (a text_pattern_ops)
+ idxpart_a_idx   |               | CREATE INDEX idxpart_a_idx ON ONLY idxpart USING btree (a text_pattern_ops)
+(6 rows)
+
+drop index idxpart_a_idx;
+create index on only idxpart (a text_pattern_ops);
+-- must reject
+alter index idxpart_a_idx attach partition idxpart2_a_idx;
+ERROR:  cannot attach index "idxpart2_a_idx" as a partition of index "idxpart_a_idx"
+DETAIL:  The index definitions do not match.
+drop table idxpart;
+-- Verify that attaching indexes maps attribute numbers correctly
+create table idxpart (col1 int, a int, col2 int, b int) partition by range (a);
+create table idxpart1 (b int, col1 int, col2 int, col3 int, a int);
+alter table idxpart drop column col1, drop column col2;
+alter table idxpart1 drop column col1, drop column col2, drop column col3;
+alter table idxpart attach partition idxpart1 for values from (0) to (1000);
+create index idxpart_1_idx on only idxpart (b, a);
+create index idxpart1_1_idx on idxpart1 (b, a);
+create index idxpart1_1b_idx on idxpart1 (b);
+-- test expressions and partial-index predicate, too
+create index idxpart_2_idx on only idxpart ((b + a)) where a > 1;
+create index idxpart1_2_idx on idxpart1 ((b + a)) where a > 1;
+create index idxpart1_2b_idx on idxpart1 ((a + b)) where a > 1;
+create index idxpart1_2c_idx on idxpart1 ((b + a)) where b > 1;
+alter index idxpart_1_idx attach partition idxpart1_1b_idx;    -- fail
+ERROR:  cannot attach index "idxpart1_1b_idx" as a partition of index "idxpart_1_idx"
+DETAIL:  The index definitions do not match.
+alter index idxpart_1_idx attach partition idxpart1_1_idx;
+alter index idxpart_2_idx attach partition idxpart1_2b_idx;    -- fail
+ERROR:  cannot attach index "idxpart1_2b_idx" as a partition of index "idxpart_2_idx"
+DETAIL:  The index definitions do not match.
+alter index idxpart_2_idx attach partition idxpart1_2c_idx;    -- fail
+ERROR:  cannot attach index "idxpart1_2c_idx" as a partition of index "idxpart_2_idx"
+DETAIL:  The index definitions do not match.
+alter index idxpart_2_idx attach partition idxpart1_2_idx; -- ok
+select relname as child, inhparent::regclass as parent, pg_get_indexdef as childdef
+  from pg_class left join pg_inherits on inhrelid = oid,
+  lateral pg_get_indexdef(pg_class.oid)
+  where relkind in ('i', 'I') and relname like 'idxpart%' order by relname;
+      child      |    parent     |                                     childdef                                     
+-----------------+---------------+----------------------------------------------------------------------------------
+ idxpart1_1_idx  | idxpart_1_idx | CREATE INDEX idxpart1_1_idx ON idxpart1 USING btree (b, a)
+ idxpart1_1b_idx |               | CREATE INDEX idxpart1_1b_idx ON idxpart1 USING btree (b)
+ idxpart1_2_idx  | idxpart_2_idx | CREATE INDEX idxpart1_2_idx ON idxpart1 USING btree (((b + a))) WHERE (a > 1)
+ idxpart1_2b_idx |               | CREATE INDEX idxpart1_2b_idx ON idxpart1 USING btree (((a + b))) WHERE (a > 1)
+ idxpart1_2c_idx |               | CREATE INDEX idxpart1_2c_idx ON idxpart1 USING btree (((b + a))) WHERE (b > 1)
+ idxpart_1_idx   |               | CREATE INDEX idxpart_1_idx ON ONLY idxpart USING btree (b, a)
+ idxpart_2_idx   |               | CREATE INDEX idxpart_2_idx ON ONLY idxpart USING btree (((b + a))) WHERE (a > 1)
+(7 rows)
+
+drop table idxpart;
+-- Make sure the partition columns are mapped correctly
+create table idxpart (a int, b int, c text) partition by range (a);
+create index idxparti on idxpart (a);
+create index idxparti2 on idxpart (c, b);
+create table idxpart1 (c text, a int, b int);
+alter table idxpart attach partition idxpart1 for values from (0) to (10);
+create table idxpart2 (c text, a int, b int);
+create index on idxpart2 (a);
+create index on idxpart2 (c, b);
+alter table idxpart attach partition idxpart2 for values from (10) to (20);
+select c.relname, pg_get_indexdef(indexrelid)
+  from pg_class c join pg_index i on c.oid = i.indexrelid
+  where indrelid::regclass::text like 'idxpart%'
+  order by indrelid::regclass::text collate "C";
+     relname      |                       pg_get_indexdef                        
+------------------+--------------------------------------------------------------
+ idxparti         | CREATE INDEX idxparti ON ONLY idxpart USING btree (a)
+ idxparti2        | CREATE INDEX idxparti2 ON ONLY idxpart USING btree (c, b)
+ idxpart1_a_idx   | CREATE INDEX idxpart1_a_idx ON idxpart1 USING btree (a)
+ idxpart1_c_b_idx | CREATE INDEX idxpart1_c_b_idx ON idxpart1 USING btree (c, b)
+ idxpart2_a_idx   | CREATE INDEX idxpart2_a_idx ON idxpart2 USING btree (a)
+ idxpart2_c_b_idx | CREATE INDEX idxpart2_c_b_idx ON idxpart2 USING btree (c, b)
+(6 rows)
+
+drop table idxpart;
+-- Verify that columns are mapped correctly in expression indexes
+create table idxpart (col1 int, col2 int, a int, b int) partition by range (a);
+create table idxpart1 (col2 int, b int, col1 int, a int);
+create table idxpart2 (col1 int, col2 int, b int, a int);
+alter table idxpart drop column col1, drop column col2;
+alter table idxpart1 drop column col1, drop column col2;
+alter table idxpart2 drop column col1, drop column col2;
+create index on idxpart2 (abs(b));
+alter table idxpart attach partition idxpart2 for values from (0) to (1);
+create index on idxpart (abs(b));
+alter table idxpart attach partition idxpart1 for values from (1) to (2);
+select c.relname, pg_get_indexdef(indexrelid)
+  from pg_class c join pg_index i on c.oid = i.indexrelid
+  where indrelid::regclass::text like 'idxpart%'
+  order by indrelid::regclass::text collate "C";
+     relname      |                          pg_get_indexdef                          
+------------------+-------------------------------------------------------------------
+ idxpart_abs_idx  | CREATE INDEX idxpart_abs_idx ON ONLY idxpart USING btree (abs(b))
+ idxpart1_abs_idx | CREATE INDEX idxpart1_abs_idx ON idxpart1 USING btree (abs(b))
+ idxpart2_abs_idx | CREATE INDEX idxpart2_abs_idx ON idxpart2 USING btree (abs(b))
+(3 rows)
+
+drop table idxpart;
+-- Verify that columns are mapped correctly for WHERE in a partial index
+create table idxpart (col1 int, a int, col3 int, b int) partition by range (a);
+alter table idxpart drop column col1, drop column col3;
+create table idxpart1 (col1 int, col2 int, col3 int, col4 int, b int, a int);
+alter table idxpart1 drop column col1, drop column col2, drop column col3, drop column col4;
+alter table idxpart attach partition idxpart1 for values from (0) to (1000);
+create table idxpart2 (col1 int, col2 int, b int, a int);
+create index on idxpart2 (a) where b > 1000;
+alter table idxpart2 drop column col1, drop column col2;
+alter table idxpart attach partition idxpart2 for values from (1000) to (2000);
+create index on idxpart (a) where b > 1000;
+select c.relname, pg_get_indexdef(indexrelid)
+  from pg_class c join pg_index i on c.oid = i.indexrelid
+  where indrelid::regclass::text like 'idxpart%'
+  order by indrelid::regclass::text collate "C";
+    relname     |                               pg_get_indexdef                               
+----------------+-----------------------------------------------------------------------------
+ idxpart_a_idx  | CREATE INDEX idxpart_a_idx ON ONLY idxpart USING btree (a) WHERE (b > 1000)
+ idxpart1_a_idx | CREATE INDEX idxpart1_a_idx ON idxpart1 USING btree (a) WHERE (b > 1000)
+ idxpart2_a_idx | CREATE INDEX idxpart2_a_idx ON idxpart2 USING btree (a) WHERE (b > 1000)
+(3 rows)
+
+drop table idxpart;
+-- Column number mapping: dropped columns in the partition
+create table idxpart1 (drop_1 int, drop_2 int, col_keep int, drop_3 int);
+alter table idxpart1 drop column drop_1;
+alter table idxpart1 drop column drop_2;
+alter table idxpart1 drop column drop_3;
+create index on idxpart1 (col_keep);
+create table idxpart (col_keep int) partition by range (col_keep);
+create index on idxpart (col_keep);
+alter table idxpart attach partition idxpart1 for values from (0) to (1000);
+\d idxpart
+               Table "public.idxpart"
+  Column  |  Type   | Collation | Nullable | Default 
+----------+---------+-----------+----------+---------
+ col_keep | integer |           |          | 
+Partition key: RANGE (col_keep)
+Indexes:
+    "idxpart_col_keep_idx" btree (col_keep)
+Number of partitions: 1 (Use \d+ to list them.)
+
+\d idxpart1
+               Table "public.idxpart1"
+  Column  |  Type   | Collation | Nullable | Default 
+----------+---------+-----------+----------+---------
+ col_keep | integer |           |          | 
+Partition of: idxpart FOR VALUES FROM (0) TO (1000)
+Indexes:
+    "idxpart1_col_keep_idx" btree (col_keep)
+
+select attrelid::regclass, attname, attnum from pg_attribute
+  where attrelid::regclass::text like 'idxpart%' and attnum > 0
+  order by attrelid::regclass, attnum;
+       attrelid        |           attname            | attnum 
+-----------------------+------------------------------+--------
+ idxpart1              | ........pg.dropped.1........ |      1
+ idxpart1              | ........pg.dropped.2........ |      2
+ idxpart1              | col_keep                     |      3
+ idxpart1              | ........pg.dropped.4........ |      4
+ idxpart1_col_keep_idx | col_keep                     |      1
+ idxpart               | col_keep                     |      1
+ idxpart_col_keep_idx  | col_keep                     |      1
+(7 rows)
+
+drop table idxpart;
+-- Column number mapping: dropped columns in the parent table
+create table idxpart(drop_1 int, drop_2 int, col_keep int, drop_3 int) partition by range (col_keep);
+alter table idxpart drop column drop_1;
+alter table idxpart drop column drop_2;
+alter table idxpart drop column drop_3;
+create table idxpart1 (col_keep int);
+create index on idxpart1 (col_keep);
+create index on idxpart (col_keep);
+alter table idxpart attach partition idxpart1 for values from (0) to (1000);
+\d idxpart
+               Table "public.idxpart"
+  Column  |  Type   | Collation | Nullable | Default 
+----------+---------+-----------+----------+---------
+ col_keep | integer |           |          | 
+Partition key: RANGE (col_keep)
+Indexes:
+    "idxpart_col_keep_idx" btree (col_keep)
+Number of partitions: 1 (Use \d+ to list them.)
+
+\d idxpart1
+               Table "public.idxpart1"
+  Column  |  Type   | Collation | Nullable | Default 
+----------+---------+-----------+----------+---------
+ col_keep | integer |           |          | 
+Partition of: idxpart FOR VALUES FROM (0) TO (1000)
+Indexes:
+    "idxpart1_col_keep_idx" btree (col_keep)
+
+select attrelid::regclass, attname, attnum from pg_attribute
+  where attrelid::regclass::text like 'idxpart%' and attnum > 0
+  order by attrelid::regclass, attnum;
+       attrelid        |           attname            | attnum 
+-----------------------+------------------------------+--------
+ idxpart               | ........pg.dropped.1........ |      1
+ idxpart               | ........pg.dropped.2........ |      2
+ idxpart               | col_keep                     |      3
+ idxpart               | ........pg.dropped.4........ |      4
+ idxpart1              | col_keep                     |      1
+ idxpart1_col_keep_idx | col_keep                     |      1
+ idxpart_col_keep_idx  | col_keep                     |      1
+(7 rows)
+
+drop table idxpart;
+-- intentionally leave some objects around
+create table idxpart (a int) partition by range (a);
+create table idxpart1 partition of idxpart for values from (0) to (100);
+create table idxpart2 partition of idxpart for values from (100) to (1000)
+  partition by range (a);
+create table idxpart21 partition of idxpart2 for values from (100) to (200);
+create table idxpart22 partition of idxpart2 for values from (200) to (300);
+create index on idxpart22 (a);
+create index on only idxpart2 (a);
+alter index idxpart2_a_idx attach partition idxpart22_a_idx;
+create index on idxpart (a);
 
 # ----------
 # Another group of parallel tests
 # ----------
-test: identity partition_join partition_prune reloptions hash_part
+test: identity partition_join partition_prune reloptions hash_part indexing
 
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
 
 test: partition_prune
 test: reloptions
 test: hash_part
+test: indexing
 test: event_trigger
 test: stats
 
 create table tab2 (x int, y tab1);
 alter table tab1 alter column b type varchar; -- fails
 
+-- Alter column type that's part of a partitioned index
+create table at_partitioned (a int, b text) partition by range (a);
+create table at_part_1 partition of at_partitioned for values from (0) to (1000);
+insert into at_partitioned values (512, '0.123');
+create table at_part_2 (b text, a int);
+insert into at_part_2 values ('1.234', 1024);
+create index on at_partitioned (b);
+create index on at_partitioned (a);
+\d at_part_1
+\d at_part_2
+alter table at_partitioned attach partition at_part_2 for values from (1000) to (2000);
+\d at_part_2
+alter table at_partitioned alter column b type numeric using b::numeric;
+\d at_part_1
+\d at_part_2
+
 -- disallow recursive containment of row types
 create temp table recur1 (f1 int);
 alter table recur1 add column f2 recur1; -- fails
 
--- /dev/null
+-- Creating an index on a partitioned table makes the partitions
+-- automatically get the index
+create table idxpart (a int, b int, c text) partition by range (a);
+create table idxpart1 partition of idxpart for values from (0) to (10);
+create table idxpart2 partition of idxpart for values from (10) to (100)
+   partition by range (b);
+create table idxpart21 partition of idxpart2 for values from (0) to (100);
+create index on idxpart (a);
+select relname, relkind, inhparent::regclass
+    from pg_class left join pg_index ix on (indexrelid = oid)
+   left join pg_inherits on (ix.indexrelid = inhrelid)
+   where relname like 'idxpart%' order by relname;
+drop table idxpart;
+
+-- Some unsupported features
+create table idxpart (a int, b int, c text) partition by range (a);
+create table idxpart1 partition of idxpart for values from (0) to (10);
+create unique index on idxpart (a);
+create index concurrently on idxpart (a);
+drop table idxpart;
+
+-- If a table without index is attached as partition to a table with
+-- an index, the index is automatically created
+create table idxpart (a int, b int, c text) partition by range (a);
+create index idxparti on idxpart (a);
+create index idxparti2 on idxpart (b, c);
+create table idxpart1 (like idxpart);
+\d idxpart1
+alter table idxpart attach partition idxpart1 for values from (0) to (10);
+\d idxpart1
+drop table idxpart;
+
+-- If a partition already has an index, don't create a duplicative one
+create table idxpart (a int, b int) partition by range (a, b);
+create table idxpart1 partition of idxpart for values from (0, 0) to (10, 10);
+create index on idxpart1 (a, b);
+create index on idxpart (a, b);
+\d idxpart1
+select relname, relkind, inhparent::regclass
+    from pg_class left join pg_index ix on (indexrelid = oid)
+   left join pg_inherits on (ix.indexrelid = inhrelid)
+   where relname like 'idxpart%' order by relname;
+drop table idxpart;
+
+-- DROP behavior for partitioned indexes
+create table idxpart (a int) partition by range (a);
+create index on idxpart (a);
+create table idxpart1 partition of idxpart for values from (0) to (10);
+drop index idxpart1_a_idx; -- no way
+drop index idxpart_a_idx;  -- both indexes go away
+select relname, relkind from pg_class
+  where relname like 'idxpart%' order by relname;
+create index on idxpart (a);
+drop table idxpart1;       -- the index on partition goes away too
+select relname, relkind from pg_class
+  where relname like 'idxpart%' order by relname;
+drop table idxpart;
+
+-- ALTER INDEX .. ATTACH, error cases
+create table idxpart (a int, b int) partition by range (a, b);
+create table idxpart1 partition of idxpart for values from (0, 0) to (10, 10);
+create index idxpart_a_b_idx on only idxpart (a, b);
+create index idxpart1_a_b_idx on idxpart1 (a, b);
+create index idxpart1_tst1 on idxpart1 (b, a);
+create index idxpart1_tst2 on idxpart1 using hash (a);
+create index idxpart1_tst3 on idxpart1 (a, b) where a > 10;
+
+alter index idxpart attach partition idxpart1;
+alter index idxpart_a_b_idx attach partition idxpart1;
+alter index idxpart_a_b_idx attach partition idxpart_a_b_idx;
+alter index idxpart_a_b_idx attach partition idxpart1_b_idx;
+alter index idxpart_a_b_idx attach partition idxpart1_tst1;
+alter index idxpart_a_b_idx attach partition idxpart1_tst2;
+alter index idxpart_a_b_idx attach partition idxpart1_tst3;
+-- OK
+alter index idxpart_a_b_idx attach partition idxpart1_a_b_idx;
+alter index idxpart_a_b_idx attach partition idxpart1_a_b_idx; -- quiet
+
+-- reject dupe
+create index idxpart1_2_a_b on idxpart1 (a, b);
+alter index idxpart_a_b_idx attach partition idxpart1_2_a_b;
+drop table idxpart;
+-- make sure everything's gone
+select indexrelid::regclass, indrelid::regclass
+  from pg_index where indexrelid::regclass::text like 'idxpart%';
+
+-- Don't auto-attach incompatible indexes
+create table idxpart (a int, b int) partition by range (a);
+create table idxpart1 (a int, b int);
+create index on idxpart1 using hash (a);
+create index on idxpart1 (a) where b > 1;
+create index on idxpart1 ((a + 0));
+create index on idxpart1 (a, a);
+create index on idxpart (a);
+alter table idxpart attach partition idxpart1 for values from (0) to (1000);
+\d idxpart1
+drop table idxpart;
+
+-- If CREATE INDEX ONLY, don't create indexes on partitions; and existing
+-- indexes on partitions don't change parent.  ALTER INDEX ATTACH can change
+-- the parent after the fact.
+create table idxpart (a int) partition by range (a);
+create table idxpart1 partition of idxpart for values from (0) to (100);
+create table idxpart2 partition of idxpart for values from (100) to (1000)
+  partition by range (a);
+create table idxpart21 partition of idxpart2 for values from (100) to (200);
+create table idxpart22 partition of idxpart2 for values from (200) to (300);
+create index on idxpart22 (a);
+create index on only idxpart2 (a);
+create index on idxpart (a);
+-- Here we expect that idxpart1 and idxpart2 have a new index, but idxpart21
+-- does not; also, idxpart22 is not attached.
+\d idxpart1
+\d idxpart2
+\d idxpart21
+select indexrelid::regclass, indrelid::regclass, inhparent::regclass
+  from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid)
+where indexrelid::regclass::text like 'idxpart%'
+  order by indrelid::regclass::text collate "C";
+alter index idxpart2_a_idx attach partition idxpart22_a_idx;
+select indexrelid::regclass, indrelid::regclass, inhparent::regclass
+  from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid)
+where indexrelid::regclass::text like 'idxpart%'
+  order by indrelid::regclass::text collate "C";
+-- attaching idxpart22 is not enough to set idxpart22_a_idx valid ...
+alter index idxpart2_a_idx attach partition idxpart22_a_idx;
+\d idxpart2
+-- ... but this one is.
+create index on idxpart21 (a);
+alter index idxpart2_a_idx attach partition idxpart21_a_idx;
+\d idxpart2
+drop table idxpart;
+
+-- When a table is attached a partition and it already has an index, a
+-- duplicate index should not get created, but rather the index becomes
+-- attached to the parent's index.
+create table idxpart (a int, b int, c text) partition by range (a);
+create index idxparti on idxpart (a);
+create index idxparti2 on idxpart (b, c);
+create table idxpart1 (like idxpart including indexes);
+\d idxpart1
+select relname, relkind, inhparent::regclass
+    from pg_class left join pg_index ix on (indexrelid = oid)
+   left join pg_inherits on (ix.indexrelid = inhrelid)
+   where relname like 'idxpart%' order by relname;
+alter table idxpart attach partition idxpart1 for values from (0) to (10);
+\d idxpart1
+select relname, relkind, inhparent::regclass
+    from pg_class left join pg_index ix on (indexrelid = oid)
+   left join pg_inherits on (ix.indexrelid = inhrelid)
+   where relname like 'idxpart%' order by relname;
+drop table idxpart;
+
+-- Verify that attaching an invalid index does not mark the parent index valid.
+-- On the other hand, attaching a valid index marks not only its direct
+-- ancestor valid, but also any indirect ancestor that was only missing the one
+-- that was just made valid
+create table idxpart (a int, b int) partition by range (a);
+create table idxpart1 partition of idxpart for values from (1) to (1000) partition by range (a);
+create table idxpart11 partition of idxpart1 for values from (1) to (100);
+create index on only idxpart1 (a);
+create index on only idxpart (a);
+-- this results in two invalid indexes:
+select relname, indisvalid from pg_class join pg_index on indexrelid = oid
+   where relname like 'idxpart%' order by relname;
+-- idxpart1_a_idx is not valid, so idxpart_a_idx should not become valid:
+alter index idxpart_a_idx attach partition idxpart1_a_idx;
+select relname, indisvalid from pg_class join pg_index on indexrelid = oid
+   where relname like 'idxpart%' order by relname;
+-- after creating and attaching this, both idxpart1_a_idx and idxpart_a_idx
+-- should become valid
+create index on idxpart11 (a);
+alter index idxpart1_a_idx attach partition idxpart11_a_idx;
+select relname, indisvalid from pg_class join pg_index on indexrelid = oid
+   where relname like 'idxpart%' order by relname;
+drop table idxpart;
+
+-- verify dependency handling during ALTER TABLE DETACH PARTITION
+create table idxpart (a int) partition by range (a);
+create table idxpart1 (like idxpart);
+create index on idxpart1 (a);
+create index on idxpart (a);
+create table idxpart2 (like idxpart);
+alter table idxpart attach partition idxpart1 for values from (0000) to (1000);
+alter table idxpart attach partition idxpart2 for values from (1000) to (2000);
+create table idxpart3 partition of idxpart for values from (2000) to (3000);
+select relname, relkind from pg_class where relname like 'idxpart%' order by relname;
+-- a) after detaching partitions, the indexes can be dropped independently
+alter table idxpart detach partition idxpart1;
+alter table idxpart detach partition idxpart2;
+alter table idxpart detach partition idxpart3;
+drop index idxpart1_a_idx;
+drop index idxpart2_a_idx;
+drop index idxpart3_a_idx;
+select relname, relkind from pg_class where relname like 'idxpart%' order by relname;
+drop table idxpart, idxpart1, idxpart2, idxpart3;
+select relname, relkind from pg_class where relname like 'idxpart%' order by relname;
+
+create table idxpart (a int) partition by range (a);
+create table idxpart1 (like idxpart);
+create index on idxpart1 (a);
+create index on idxpart (a);
+create table idxpart2 (like idxpart);
+alter table idxpart attach partition idxpart1 for values from (0000) to (1000);
+alter table idxpart attach partition idxpart2 for values from (1000) to (2000);
+create table idxpart3 partition of idxpart for values from (2000) to (3000);
+-- b) after detaching, dropping the index on parent does not remove the others
+select relname, relkind from pg_class where relname like 'idxpart%' order by relname;
+alter table idxpart detach partition idxpart1;
+alter table idxpart detach partition idxpart2;
+alter table idxpart detach partition idxpart3;
+drop index idxpart_a_idx;
+select relname, relkind from pg_class where relname like 'idxpart%' order by relname;
+drop table idxpart, idxpart1, idxpart2, idxpart3;
+select relname, relkind from pg_class where relname like 'idxpart%' order by relname;
+
+-- Verify that expression indexes inherit correctly
+create table idxpart (a int, b int) partition by range (a);
+create table idxpart1 (like idxpart);
+create index on idxpart1 ((a + b));
+create index on idxpart ((a + b));
+create table idxpart2 (like idxpart);
+alter table idxpart attach partition idxpart1 for values from (0000) to (1000);
+alter table idxpart attach partition idxpart2 for values from (1000) to (2000);
+create table idxpart3 partition of idxpart for values from (2000) to (3000);
+select relname as child, inhparent::regclass as parent, pg_get_indexdef as childdef
+  from pg_class join pg_inherits on inhrelid = oid,
+  lateral pg_get_indexdef(pg_class.oid)
+  where relkind in ('i', 'I') and relname like 'idxpart%' order by relname;
+drop table idxpart;
+
+-- Verify behavior for collation (mis)matches
+create table idxpart (a text) partition by range (a);
+create table idxpart1 (like idxpart);
+create table idxpart2 (like idxpart);
+create index on idxpart2 (a collate "POSIX");
+create index on idxpart2 (a);
+create index on idxpart2 (a collate "C");
+alter table idxpart attach partition idxpart1 for values from ('aaa') to ('bbb');
+alter table idxpart attach partition idxpart2 for values from ('bbb') to ('ccc');
+create table idxpart3 partition of idxpart for values from ('ccc') to ('ddd');
+create index on idxpart (a collate "C");
+create table idxpart4 partition of idxpart for values from ('ddd') to ('eee');
+select relname as child, inhparent::regclass as parent, pg_get_indexdef as childdef
+  from pg_class left join pg_inherits on inhrelid = oid,
+  lateral pg_get_indexdef(pg_class.oid)
+  where relkind in ('i', 'I') and relname like 'idxpart%' order by relname;
+drop table idxpart;
+
+-- Verify behavior for opclass (mis)matches
+create table idxpart (a text) partition by range (a);
+create table idxpart1 (like idxpart);
+create table idxpart2 (like idxpart);
+create index on idxpart2 (a);
+alter table idxpart attach partition idxpart1 for values from ('aaa') to ('bbb');
+alter table idxpart attach partition idxpart2 for values from ('bbb') to ('ccc');
+create table idxpart3 partition of idxpart for values from ('ccc') to ('ddd');
+create index on idxpart (a text_pattern_ops);
+create table idxpart4 partition of idxpart for values from ('ddd') to ('eee');
+-- must *not* have attached the index we created on idxpart2
+select relname as child, inhparent::regclass as parent, pg_get_indexdef as childdef
+  from pg_class left join pg_inherits on inhrelid = oid,
+  lateral pg_get_indexdef(pg_class.oid)
+  where relkind in ('i', 'I') and relname like 'idxpart%' order by relname;
+drop index idxpart_a_idx;
+create index on only idxpart (a text_pattern_ops);
+-- must reject
+alter index idxpart_a_idx attach partition idxpart2_a_idx;
+drop table idxpart;
+
+-- Verify that attaching indexes maps attribute numbers correctly
+create table idxpart (col1 int, a int, col2 int, b int) partition by range (a);
+create table idxpart1 (b int, col1 int, col2 int, col3 int, a int);
+alter table idxpart drop column col1, drop column col2;
+alter table idxpart1 drop column col1, drop column col2, drop column col3;
+alter table idxpart attach partition idxpart1 for values from (0) to (1000);
+create index idxpart_1_idx on only idxpart (b, a);
+create index idxpart1_1_idx on idxpart1 (b, a);
+create index idxpart1_1b_idx on idxpart1 (b);
+-- test expressions and partial-index predicate, too
+create index idxpart_2_idx on only idxpart ((b + a)) where a > 1;
+create index idxpart1_2_idx on idxpart1 ((b + a)) where a > 1;
+create index idxpart1_2b_idx on idxpart1 ((a + b)) where a > 1;
+create index idxpart1_2c_idx on idxpart1 ((b + a)) where b > 1;
+alter index idxpart_1_idx attach partition idxpart1_1b_idx;    -- fail
+alter index idxpart_1_idx attach partition idxpart1_1_idx;
+alter index idxpart_2_idx attach partition idxpart1_2b_idx;    -- fail
+alter index idxpart_2_idx attach partition idxpart1_2c_idx;    -- fail
+alter index idxpart_2_idx attach partition idxpart1_2_idx; -- ok
+select relname as child, inhparent::regclass as parent, pg_get_indexdef as childdef
+  from pg_class left join pg_inherits on inhrelid = oid,
+  lateral pg_get_indexdef(pg_class.oid)
+  where relkind in ('i', 'I') and relname like 'idxpart%' order by relname;
+drop table idxpart;
+
+-- Make sure the partition columns are mapped correctly
+create table idxpart (a int, b int, c text) partition by range (a);
+create index idxparti on idxpart (a);
+create index idxparti2 on idxpart (c, b);
+create table idxpart1 (c text, a int, b int);
+alter table idxpart attach partition idxpart1 for values from (0) to (10);
+create table idxpart2 (c text, a int, b int);
+create index on idxpart2 (a);
+create index on idxpart2 (c, b);
+alter table idxpart attach partition idxpart2 for values from (10) to (20);
+select c.relname, pg_get_indexdef(indexrelid)
+  from pg_class c join pg_index i on c.oid = i.indexrelid
+  where indrelid::regclass::text like 'idxpart%'
+  order by indrelid::regclass::text collate "C";
+drop table idxpart;
+
+-- Verify that columns are mapped correctly in expression indexes
+create table idxpart (col1 int, col2 int, a int, b int) partition by range (a);
+create table idxpart1 (col2 int, b int, col1 int, a int);
+create table idxpart2 (col1 int, col2 int, b int, a int);
+alter table idxpart drop column col1, drop column col2;
+alter table idxpart1 drop column col1, drop column col2;
+alter table idxpart2 drop column col1, drop column col2;
+create index on idxpart2 (abs(b));
+alter table idxpart attach partition idxpart2 for values from (0) to (1);
+create index on idxpart (abs(b));
+alter table idxpart attach partition idxpart1 for values from (1) to (2);
+select c.relname, pg_get_indexdef(indexrelid)
+  from pg_class c join pg_index i on c.oid = i.indexrelid
+  where indrelid::regclass::text like 'idxpart%'
+  order by indrelid::regclass::text collate "C";
+drop table idxpart;
+
+-- Verify that columns are mapped correctly for WHERE in a partial index
+create table idxpart (col1 int, a int, col3 int, b int) partition by range (a);
+alter table idxpart drop column col1, drop column col3;
+create table idxpart1 (col1 int, col2 int, col3 int, col4 int, b int, a int);
+alter table idxpart1 drop column col1, drop column col2, drop column col3, drop column col4;
+alter table idxpart attach partition idxpart1 for values from (0) to (1000);
+create table idxpart2 (col1 int, col2 int, b int, a int);
+create index on idxpart2 (a) where b > 1000;
+alter table idxpart2 drop column col1, drop column col2;
+alter table idxpart attach partition idxpart2 for values from (1000) to (2000);
+create index on idxpart (a) where b > 1000;
+select c.relname, pg_get_indexdef(indexrelid)
+  from pg_class c join pg_index i on c.oid = i.indexrelid
+  where indrelid::regclass::text like 'idxpart%'
+  order by indrelid::regclass::text collate "C";
+drop table idxpart;
+
+-- Column number mapping: dropped columns in the partition
+create table idxpart1 (drop_1 int, drop_2 int, col_keep int, drop_3 int);
+alter table idxpart1 drop column drop_1;
+alter table idxpart1 drop column drop_2;
+alter table idxpart1 drop column drop_3;
+create index on idxpart1 (col_keep);
+create table idxpart (col_keep int) partition by range (col_keep);
+create index on idxpart (col_keep);
+alter table idxpart attach partition idxpart1 for values from (0) to (1000);
+\d idxpart
+\d idxpart1
+select attrelid::regclass, attname, attnum from pg_attribute
+  where attrelid::regclass::text like 'idxpart%' and attnum > 0
+  order by attrelid::regclass, attnum;
+drop table idxpart;
+
+-- Column number mapping: dropped columns in the parent table
+create table idxpart(drop_1 int, drop_2 int, col_keep int, drop_3 int) partition by range (col_keep);
+alter table idxpart drop column drop_1;
+alter table idxpart drop column drop_2;
+alter table idxpart drop column drop_3;
+create table idxpart1 (col_keep int);
+create index on idxpart1 (col_keep);
+create index on idxpart (col_keep);
+alter table idxpart attach partition idxpart1 for values from (0) to (1000);
+\d idxpart
+\d idxpart1
+select attrelid::regclass, attname, attnum from pg_attribute
+  where attrelid::regclass::text like 'idxpart%' and attnum > 0
+  order by attrelid::regclass, attnum;
+drop table idxpart;
+
+-- intentionally leave some objects around
+create table idxpart (a int) partition by range (a);
+create table idxpart1 partition of idxpart for values from (0) to (100);
+create table idxpart2 partition of idxpart for values from (100) to (1000)
+  partition by range (a);
+create table idxpart21 partition of idxpart2 for values from (100) to (200);
+create table idxpart22 partition of idxpart2 for values from (200) to (300);
+create index on idxpart22 (a);
+create index on only idxpart2 (a);
+alter index idxpart2_a_idx attach partition idxpart22_a_idx;
+create index on idxpart (a);