Alvaro Herrera wrote:
> Simon Riggs wrote:
> > On 13 November 2017 at 12:55, Alvaro Herrera <alvhe...@alvh.no-ip.org> 
> > wrote:
> > > Somehow I managed to include an unrelated patch as attachment.  Here's
> > > another attempt (on which I also lightly touched ddl.sgml with relevant
> > > changes).
> > 
> > Looks good. Some minor comments below.
> > 
> > 0001- Simplify
> > Seems useful as separate step; agree with everything, no further comments
> 
> Thanks, pushed.

Here's the remaining bits, rebased.

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
>From c9bb7365be8cc542660379632369bfebe5e2d64b Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvhe...@alvh.no-ip.org>
Date: Thu, 26 Oct 2017 11:15:39 +0200
Subject: [PATCH v5 1/4] Get rid of copy_partition_key

Instead, allocate the objects in a temporary context right from the
start, and only make it permanent once no errors can occur anymore.

Reviewed-by: Robert Haas, Tom Lane
Discussion: https://postgr.es/m/20171027172730.eh2domlkpn4ja62m@alvherre.pgsql
---
 src/backend/utils/cache/relcache.c | 69 +++++---------------------------------
 1 file changed, 8 insertions(+), 61 deletions(-)

diff --git a/src/backend/utils/cache/relcache.c 
b/src/backend/utils/cache/relcache.c
index 1908420d82..59280cd8bb 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -262,7 +262,6 @@ static Relation AllocateRelationDesc(Form_pg_class relp);
 static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
 static void RelationBuildTupleDesc(Relation relation);
 static void RelationBuildPartitionKey(Relation relation);
-static PartitionKey copy_partition_key(PartitionKey fromkey);
 static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
 static void RelationInitPhysicalAddr(Relation relation);
 static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -846,6 +845,11 @@ RelationBuildPartitionKey(Relation relation)
        if (!HeapTupleIsValid(tuple))
                return;
 
+       partkeycxt = AllocSetContextCreate(CurTransactionContext,
+                                                                          
RelationGetRelationName(relation),
+                                                                          
ALLOCSET_SMALL_SIZES);
+       oldcxt = MemoryContextSwitchTo(partkeycxt);
+
        key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
 
        /* Fixed-length attributes */
@@ -983,71 +987,14 @@ RelationBuildPartitionKey(Relation relation)
 
        ReleaseSysCache(tuple);
 
-       /* Success --- now copy to the cache memory */
-       partkeycxt = AllocSetContextCreate(CacheMemoryContext,
-                                                                          
RelationGetRelationName(relation),
-                                                                          
ALLOCSET_SMALL_SIZES);
+       /* Success --- make the relcache point to the newly constructed key */
+       MemoryContextSetParent(partkeycxt, CacheMemoryContext);
        relation->rd_partkeycxt = partkeycxt;
-       oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);
-       relation->rd_partkey = copy_partition_key(key);
+       relation->rd_partkey = key;
        MemoryContextSwitchTo(oldcxt);
 }
 
 /*
- * copy_partition_key
- *
- * The copy is allocated in the current memory context.
- */
-static PartitionKey
-copy_partition_key(PartitionKey fromkey)
-{
-       PartitionKey newkey;
-       int                     n;
-
-       newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));
-
-       newkey->strategy = fromkey->strategy;
-       newkey->partnatts = n = fromkey->partnatts;
-
-       newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));
-       memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));
-
-       newkey->partexprs = copyObject(fromkey->partexprs);
-
-       newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));
-       memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));
-
-       newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));
-       memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));
-
-       newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));
-       memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));
-
-       newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));
-       memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));
-
-       newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));
-       memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));
-
-       newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));
-       memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));
-
-       newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));
-       memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));
-
-       newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));
-       memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));
-
-       newkey->parttypalign = (char *) palloc(n * sizeof(bool));
-       memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));
-
-       newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));
-       memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));
-
-       return newkey;
-}
-
-/*
  *             equalRuleLocks
  *
  *             Determine whether two RuleLocks are equivalent
-- 
2.11.0

>From 3ef8b166e3417a537c50d18567a57a76df4e85b0 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvhe...@alvh.no-ip.org>
Date: Mon, 23 Oct 2017 10:18:38 +0200
Subject: [PATCH v5 2/4] export generateClonedIndexStmt

---
 src/backend/nodes/copyfuncs.c      |  1 +
 src/backend/nodes/equalfuncs.c     |  1 +
 src/backend/nodes/outfuncs.c       |  1 +
 src/backend/parser/gram.y          |  1 +
 src/backend/parser/parse_utilcmd.c | 19 +++++++++++--------
 src/include/nodes/parsenodes.h     |  4 ++++
 src/include/parser/parse_utilcmd.h |  3 +++
 7 files changed, 22 insertions(+), 8 deletions(-)

diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 76e75459b4..95575ff4e8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3365,6 +3365,7 @@ _copyIndexStmt(const IndexStmt *from)
 
        COPY_STRING_FIELD(idxname);
        COPY_NODE_FIELD(relation);
+       COPY_SCALAR_FIELD(relationId);
        COPY_STRING_FIELD(accessMethod);
        COPY_STRING_FIELD(tableSpace);
        COPY_NODE_FIELD(indexParams);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 2866fd7b4a..788ce9c756 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1324,6 +1324,7 @@ _equalIndexStmt(const IndexStmt *a, const IndexStmt *b)
 {
        COMPARE_STRING_FIELD(idxname);
        COMPARE_NODE_FIELD(relation);
+       COMPARE_SCALAR_FIELD(relationId);
        COMPARE_STRING_FIELD(accessMethod);
        COMPARE_STRING_FIELD(tableSpace);
        COMPARE_NODE_FIELD(indexParams);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index dc35df9e4f..e7b7a6b53d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2645,6 +2645,7 @@ _outIndexStmt(StringInfo str, const IndexStmt *node)
 
        WRITE_STRING_FIELD(idxname);
        WRITE_NODE_FIELD(relation);
+       WRITE_OID_FIELD(relationId);
        WRITE_STRING_FIELD(accessMethod);
        WRITE_STRING_FIELD(tableSpace);
        WRITE_NODE_FIELD(indexParams);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c301ca465d..78cbf8029c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -7236,6 +7236,7 @@ IndexStmt:        CREATE opt_unique INDEX 
opt_concurrently opt_index_name
                                        n->concurrent = $4;
                                        n->idxname = $5;
                                        n->relation = $7;
+                                       n->relationId = InvalidOid;
                                        n->accessMethod = $8;
                                        n->indexParams = $10;
                                        n->options = $12;
diff --git a/src/backend/parser/parse_utilcmd.c 
b/src/backend/parser/parse_utilcmd.c
index 8461da490a..f9e8f6da0d 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -117,9 +117,6 @@ static void transformTableLikeClause(CreateStmtContext *cxt,
                                                 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);
@@ -1173,7 +1170,8 @@ transformTableLikeClause(CreateStmtContext *cxt, 
TableLikeClause *table_like_cla
                        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 */
@@ -1251,10 +1249,12 @@ transformOfType(CreateStmtContext *cxt, TypeName 
*ofTypename)
 
 /*
  * 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);
@@ -1275,6 +1275,8 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation 
source_idx,
        Datum           datum;
        bool            isnull;
 
+       Assert((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.
@@ -1310,7 +1312,8 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation 
source_idx,
 
        /* 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);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 34d6afc80f..b4ce3548dc 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2698,6 +2698,9 @@ typedef struct FetchStmt
  * 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
+ * or by OID.
  * ----------------------
  */
 typedef struct IndexStmt
@@ -2705,6 +2708,7 @@ 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 */
diff --git a/src/include/parser/parse_utilcmd.h 
b/src/include/parser/parse_utilcmd.h
index e749432ef0..599f0e8e29 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -27,5 +27,8 @@ extern void transformRuleStmt(RuleStmt *stmt, const char 
*queryString,
 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 */
-- 
2.11.0

>From cda0e9fcfe843c594666306fdce6b2ba6d074f5c Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvhe...@alvh.no-ip.org>
Date: Fri, 6 Oct 2017 18:05:50 +0200
Subject: [PATCH v5 3/4] Allow indexes on partitioned tables

---
 doc/src/sgml/ref/alter_index.sgml         |  28 ++-
 doc/src/sgml/ref/alter_table.sgml         |   8 +-
 doc/src/sgml/ref/create_index.sgml        |  27 ++-
 src/backend/access/common/reloptions.c    |   1 +
 src/backend/access/heap/heapam.c          |   9 +-
 src/backend/access/index/indexam.c        |   3 +-
 src/backend/bootstrap/bootparse.y         |   2 +
 src/backend/catalog/aclchk.c              |   9 +-
 src/backend/catalog/dependency.c          |   3 +-
 src/backend/catalog/heap.c                |   1 +
 src/backend/catalog/index.c               | 101 ++++++++-
 src/backend/catalog/objectaddress.c       |   5 +-
 src/backend/catalog/pg_depend.c           |  13 +-
 src/backend/catalog/toasting.c            |   1 +
 src/backend/commands/indexcmds.c          | 288 +++++++++++++++++++++--
 src/backend/commands/tablecmds.c          | 364 +++++++++++++++++++++++++++++-
 src/backend/parser/gram.y                 |  44 +++-
 src/backend/parser/parse_utilcmd.c        |  45 +++-
 src/backend/tcop/utility.c                |   9 +
 src/backend/utils/adt/amutils.c           |   3 +-
 src/backend/utils/adt/ruleutils.c         |   4 +-
 src/backend/utils/cache/relcache.c        |  30 ++-
 src/bin/pg_dump/common.c                  |  70 ++++++
 src/bin/pg_dump/pg_dump.c                 |  57 ++++-
 src/bin/pg_dump/pg_dump.h                 |   4 +
 src/bin/psql/describe.c                   |  20 +-
 src/include/catalog/catversion.h          |   2 +-
 src/include/catalog/index.h               |   6 +
 src/include/catalog/pg_class.h            |   1 +
 src/include/catalog/pg_index.h            |  38 ++--
 src/include/commands/defrem.h             |   3 +-
 src/include/nodes/parsenodes.h            |   5 +-
 src/test/regress/expected/alter_table.out |   4 +-
 src/test/regress/expected/indexing.out    | 330 +++++++++++++++++++++++++++
 src/test/regress/parallel_schedule        |   2 +-
 src/test/regress/serial_schedule          |   1 +
 src/test/regress/sql/indexing.sql         | 142 ++++++++++++
 37 files changed, 1574 insertions(+), 109 deletions(-)
 create mode 100644 src/test/regress/expected/indexing.out
 create mode 100644 src/test/regress/sql/indexing.sql

diff --git a/doc/src/sgml/ref/alter_index.sgml 
b/doc/src/sgml/ref/alter_index.sgml
index 5d0b792e50..e701103987 100644
--- a/doc/src/sgml/ref/alter_index.sgml
+++ b/doc/src/sgml/ref/alter_index.sgml
@@ -23,7 +23,9 @@ PostgreSQL documentation
 <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> DEPENDS ON 
EXTENSION <replaceable class="parameter">extension_name</replaceable>
+ALTER INDEX <replaceable class="parameter">name</replaceable> ATTACH 
<replaceable class="parameter">index_name</replaceable>
+ALTER INDEX <replaceable class="parameter">name</replaceable> DETACH 
<replaceable class="parameter">index_name</replaceable>
+ALTER INDEX  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> [, ... ] 
)
 ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> 
ALTER [ COLUMN ] <replaceable class="parameter">column_number</replaceable>
@@ -76,6 +78,30 @@ ALTER INDEX ALL IN TABLESPACE <replaceable 
class="parameter">name</replaceable>
    </varlistentry>
 
    <varlistentry>
+    <term><literal>ATTACH</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>DETACH</literal></term>
+    <listitem>
+     <para>
+      Causes the named index to become detached from the altered index,
+      severing their link and turning them into separately-operable
+      objects.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry>
     <term><literal>DEPENDS ON EXTENSION</literal></term>
     <listitem>
      <para>
diff --git a/doc/src/sgml/ref/alter_table.sgml 
b/doc/src/sgml/ref/alter_table.sgml
index 3b19ea7131..0b40aaa17c 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -783,7 +783,10 @@ ALTER TABLE [ IF EXISTS ] <replaceable 
class="parameter">name</replaceable>
       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</command> had been executed.
      </para>
 
      <para>
@@ -844,7 +847,8 @@ ALTER TABLE [ IF EXISTS ] <replaceable 
class="parameter">name</replaceable>
      <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>
diff --git a/doc/src/sgml/ref/create_index.sgml 
b/doc/src/sgml/ref/create_index.sgml
index 92c0090dfd..6f923e61a2 100644
--- a/doc/src/sgml/ref/create_index.sgml
+++ b/doc/src/sgml/ref/create_index.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
 
  <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> ]
@@ -152,6 +152,16 @@ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS 
] <replaceable class=
      </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>
        <para>
@@ -546,6 +556,21 @@ Indexes:
   </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 a matching indexes.  Each partition is first
+   checked to determine whether equivalent index already exists,
+   and if so, that index will become attached as a partition index to
+   the index being created, which will be its parent index.  If no
+   matching index exists, a new index will be created and automatically
+   attached.  If <literal>ONLY</literal> is specified, no recursion
+   is done.  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
    FIRST</literal>, and/or <literal>NULLS LAST</literal> can be specified to 
modify
diff --git a/src/backend/access/common/reloptions.c 
b/src/backend/access/common/reloptions.c
index 3d0ce9af6f..c9cea08ace 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -983,6 +983,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
                        options = view_reloptions(datum, false);
                        break;
                case RELKIND_INDEX:
+               case RELKIND_PARTITIONED_INDEX:
                        options = index_reloptions(amoptions, datum, false);
                        break;
                case RELKIND_FOREIGN_TABLE:
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 3acef279f4..b304944913 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1293,7 +1293,8 @@ heap_open(Oid relationId, LOCKMODE lockmode)
 
        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",
@@ -1321,7 +1322,8 @@ heap_openrv(const RangeVar *relation, LOCKMODE lockmode)
 
        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",
@@ -1353,7 +1355,8 @@ heap_openrv_extended(const RangeVar *relation, LOCKMODE 
lockmode,
 
        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",
diff --git a/src/backend/access/index/indexam.c 
b/src/backend/access/index/indexam.c
index edf4172eb2..c60db1eef5 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -154,7 +154,8 @@ index_open(Oid relationId, LOCKMODE lockmode)
 
        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",
diff --git a/src/backend/bootstrap/bootparse.y 
b/src/backend/bootstrap/bootparse.y
index 2e1fef0350..95835ac1e7 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -321,6 +321,7 @@ Boot_DeclareIndexStmt:
                                        DefineIndex(relationId,
                                                                stmt,
                                                                $4,
+                                                               InvalidOid,
                                                                false,
                                                                false,
                                                                false,
@@ -365,6 +366,7 @@ Boot_DeclareUniqueIndexStmt:
                                        DefineIndex(relationId,
                                                                stmt,
                                                                $5,
+                                                               InvalidOid,
                                                                false,
                                                                false,
                                                                false,
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index ccde66a7dd..89e95ace37 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -1766,7 +1766,8 @@ ExecGrant_Relation(InternalGrant *istmt)
                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",
@@ -5347,7 +5348,8 @@ recordExtObjInitPriv(Oid objoid, Oid classoid)
                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 */
@@ -5632,7 +5634,8 @@ removeExtObjInitPriv(Oid objoid, Oid classoid)
                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 */
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 033c4358ea..720c6dd0bd 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1109,7 +1109,8 @@ doDeletion(const ObjectAddress *object, int flags)
                        {
                                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);
 
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 9e14880b99..bc9fba9dfa 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -294,6 +294,7 @@ heap_create(const char *relname,
                case RELKIND_COMPOSITE_TYPE:
                case RELKIND_FOREIGN_TABLE:
                case RELKIND_PARTITIONED_TABLE:
+               case RELKIND_PARTITIONED_INDEX:
                        create_storage = false;
 
                        /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 0125c18bc1..9346fef6b4 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -41,6 +41,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_constraint_fn.h"
+#include "catalog/pg_depend.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_tablespace.h"
@@ -98,6 +99,7 @@ static void InitializeAttributeOids(Relation indexRelation,
                                                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,
@@ -551,6 +553,7 @@ AppendAttributeTuples(Relation indexRelation, int numatts)
 static void
 UpdateIndexRelation(Oid indexoid,
                                        Oid heapoid,
+                                       Oid parentIndexOid,
                                        IndexInfo *indexInfo,
                                        Oid *collationOids,
                                        Oid *classOids,
@@ -624,6 +627,7 @@ UpdateIndexRelation(Oid indexoid,
 
        values[Anum_pg_index_indexrelid - 1] = ObjectIdGetDatum(indexoid);
        values[Anum_pg_index_indrelid - 1] = ObjectIdGetDatum(heapoid);
+       values[Anum_pg_index_indparentidx - 1 ] = 
ObjectIdGetDatum(parentIndexOid);
        values[Anum_pg_index_indnatts - 1] = 
Int16GetDatum(indexInfo->ii_NumIndexAttrs);
        values[Anum_pg_index_indisunique - 1] = 
BoolGetDatum(indexInfo->ii_Unique);
        values[Anum_pg_index_indisprimary - 1] = BoolGetDatum(primary);
@@ -670,6 +674,8 @@ UpdateIndexRelation(Oid indexoid,
  * 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
@@ -695,6 +701,8 @@ UpdateIndexRelation(Oid indexoid,
  *             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
@@ -706,6 +714,7 @@ Oid
 index_create(Relation heapRelation,
                         const char *indexRelationName,
                         Oid indexRelationId,
+                        Oid parentIndexRelid,
                         Oid relFileNode,
                         IndexInfo *indexInfo,
                         List *indexColNames,
@@ -732,11 +741,16 @@ index_create(Relation heapRelation,
        char            relpersistence;
        bool            isprimary = (flags & INDEX_CREATE_IS_PRIMARY) != 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);
@@ -864,7 +878,7 @@ index_create(Relation heapRelation,
                                                                indexRelationId,
                                                                relFileNode,
                                                                indexTupDesc,
-                                                               RELKIND_INDEX,
+                                                               relkind,
                                                                relpersistence,
                                                                shared_relation,
                                                                mapped_relation,
@@ -921,7 +935,8 @@ index_create(Relation heapRelation,
         *        (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,
@@ -1010,6 +1025,17 @@ index_create(Relation heapRelation,
                        }
                }
 
+               /* Store dependency on parent index, if any */
+               if (OidIsValid(parentIndexRelid))
+               {
+                       referenced.classId = RelationRelationId;
+                       referenced.objectId = parentIndexRelid;
+                       referenced.objectSubId = 0;
+
+                       recordDependencyOn(&myself, &referenced, 
DEPENDENCY_INTERNAL);
+               }
+
+
                /* Store dependency on collations */
                /* The default collation is pinned, so don't bother recording 
it */
                for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
@@ -1555,9 +1581,10 @@ index_drop(Oid indexId, bool concurrent)
        }
 
        /*
-        * 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
@@ -1700,6 +1727,45 @@ BuildIndexInfo(Relation index)
        return ii;
 }
 
+/*
+ * Compare two IndexInfos, and return true if they are similar enough that an
+ * index built with one can pass as an index built with the other.  If an
+ * attmap is given, the indexes are from tables where the columns might be in
+ * different physical locations, so use the map to match the column by name.
+ */
+bool
+CompareIndexInfo(IndexInfo *info1, IndexInfo *info2, AttrNumber *attmap)
+{
+       int             i;
+
+       if (info1->ii_NumIndexAttrs != info2->ii_NumIndexAttrs)
+               return false;
+
+       for (i = 0; i < info1->ii_NumIndexAttrs; i++)
+       {
+               /* XXX use attmap here */
+               if (info1->ii_KeyAttrNumbers[i] != info2->ii_KeyAttrNumbers[i])
+                       return false;
+       }
+
+       /* Expression indexes are currently not considered equal.  Someday ... 
*/
+       if (info1->ii_Expressions != NIL || info2->ii_Expressions != NIL)
+               return false;
+
+       /* Can this be relaxed? */
+       if (!equal(info1->ii_Predicate, info2->ii_Predicate))
+               return false;
+
+       /* Probably this can be relaxed someday */
+       if (info1->ii_ExclusionOps != NULL || info2->ii_ExclusionOps != NULL)
+               return false;
+
+       if (info1->ii_Unique != info2->ii_Unique)
+               return false;
+
+       return true;
+}
+
 /* ----------------
  *             BuildSpeculativeIndexInfo
  *                     Add extra state to IndexInfo record
@@ -1922,6 +1988,9 @@ index_update_stats(Relation rel,
                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;
@@ -3332,6 +3401,14 @@ reindex_index(Oid indexId, bool skip_constraint_checks, 
char persistence,
        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.
         */
@@ -3530,6 +3607,22 @@ reindex_relation(Oid relid, int flags, int options)
         */
        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;
 
        /*
diff --git a/src/backend/catalog/objectaddress.c 
b/src/backend/catalog/objectaddress.c
index 8d55c76fc4..0f7c7318f4 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1216,7 +1216,8 @@ get_relation_by_qualified_name(ObjectType objtype, List 
*object,
        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",
@@ -3476,6 +3477,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
                                                         relname);
                        break;
                case RELKIND_INDEX:
+               case RELKIND_PARTITIONED_INDEX:
                        appendStringInfo(buffer, _("index %s"),
                                                         relname);
                        break;
@@ -3950,6 +3952,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, 
int32 objectSubId)
                        appendStringInfoString(buffer, "table");
                        break;
                case RELKIND_INDEX:
+               case RELKIND_PARTITIONED_INDEX:
                        appendStringInfoString(buffer, "index");
                        break;
                case RELKIND_SEQUENCE:
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index cf0086b9bd..53db972dec 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -656,14 +656,19 @@ get_constraint_index(Oid constraintId)
 
                /*
                 * 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;
                }
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 539ca79ad3..b1be2bee36 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -328,6 +328,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid 
toastIndexOid,
        coloptions[1] = 0;
 
        index_create(toast_rel, toast_idxname, toastIndexOid, InvalidOid,
+                                InvalidOid,
                                 indexInfo,
                                 list_make2("chunk_id", "chunk_seq"),
                                 BTREE_AM_OID,
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 97091dd9fb..df8ec66929 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -23,7 +23,9 @@
 #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_fn.h"
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_opfamily.h"
 #include "catalog/pg_tablespace.h"
@@ -35,6 +37,7 @@
 #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"
@@ -77,6 +80,7 @@ static char *ChooseIndexNameAddition(List *colnames);
 static List *ChooseIndexColumnNames(List *indexElems);
 static void RangeVarCallbackForReindexIndex(const RangeVar *relation,
                                                                Oid relId, Oid 
oldRelId, void *arg);
+static void ReindexPartitionedIndex(Relation parentIdx);
 
 /*
  * CheckIndexCompatible
@@ -292,14 +296,15 @@ CheckIndexCompatible(Oid oldId,
  * '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.
@@ -308,6 +313,7 @@ ObjectAddress
 DefineIndex(Oid relationId,
                        IndexStmt *stmt,
                        Oid indexRelationId,
+                       Oid parentIndexId,
                        bool is_alter_table,
                        bool check_rights,
                        bool check_not_in_use,
@@ -330,6 +336,7 @@ DefineIndex(Oid relationId,
        IndexAmRoutine *amRoutine;
        bool            amcanorder;
        amoptions_function amoptions;
+       bool            partitioned;
        Datum           reloptions;
        int16      *coloptions;
        IndexInfo  *indexInfo;
@@ -382,23 +389,56 @@ DefineIndex(Oid relationId,
        {
                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 (is_alter_table)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("cannot create constraints on 
partitioned tables")));
        }
 
        /*
@@ -665,17 +705,20 @@ DefineIndex(Oid relationId,
        /*
         * 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;
 
@@ -685,8 +728,8 @@ DefineIndex(Oid relationId,
                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,
@@ -706,6 +749,145 @@ DefineIndex(Oid relationId,
                CreateComments(indexRelationId, RelationRelationId, 0,
                                           stmt->idxcomment);
 
+       if (partitioned)
+       {
+               /*
+                * If ONLY was specified, we're done.  Otherwise, recurse to 
each
+                * partition to create the corresponding index, or flag an 
existing
+                * index as a children of this one.
+                *
+                * If we're invoked internally, recurse always.
+                */
+               if ((stmt->relation && stmt->relation->inh) || !stmt->relation)
+               {
+                       PartitionDesc partdesc = RelationGetPartitionDesc(rel);
+                       Oid                *part_oids;
+                       int                     nparts;
+                       MemoryContext oldcxt;
+                       TupleDesc       parentDesc;
+
+                       /*
+                        * XXX In this block, we're going to call DefineIndex 
recursively
+                        * on the partitions; when building CONCURRENTLY, this 
will close
+                        * and open one transaction each time.  If indexes 
exist on some
+                        * partitions, we will not use separate transactions 
for those.
+                        * That seems okay, since the changes done in that case 
will be
+                        * catalog only and should not take long.
+                        *
+                        * XXX Note that CONCURRENTLY is not implemented yet for
+                        * partitioned tables, though; make sure to fix any 
holes there
+                        * before enabling that ...
+                        */
+
+                       /*
+                        * For the concurrent case, we must ensure not to lose 
the array
+                        * of partitions we're going to work on, so copy it out 
of relcache.
+                        * PortalContext has the lifetime we need.  Also, save 
a copy of
+                        * the relation's tuple desc.
+                        */
+                       oldcxt = MemoryContextSwitchTo(PortalContext);
+
+                       nparts = partdesc->nparts;
+                       part_oids = palloc(sizeof(Oid) * nparts);
+                       memcpy(part_oids, partdesc->oids, sizeof(Oid) * nparts);
+
+                       parentDesc = 
palloc(TupleDescSize(RelationGetDescr(rel)));
+                       TupleDescCopy(parentDesc, RelationGetDescr(rel));
+
+                       MemoryContextSwitchTo(oldcxt);
+
+                       heap_close(rel, NoLock);
+
+                       /*
+                        * Now recurse, one child at a time.  Note that if any 
child is in
+                        * turn a partitioned table, this will recursively do 
the right thing.
+                        * In the concurrent case, this will close the current 
transaction and
+                        * open a new one.
+                        *
+                        * XXX Verify this coding carefully; see also 
ATExecAttachPartition
+                        */
+                       for (i = 0; i < nparts; i++)
+                       {
+                               Oid             childRelid = part_oids[i];
+                               Relation childrel;
+                               List   *childidxs;
+                               ListCell *cell;
+                               AttrNumber *attmap = NULL;
+                               bool    found = false;
+
+                               childrel = heap_open(childRelid, lockmode);
+                               /* childidxs goes away with transaction in 
concurrent mode;
+                                * we leak it otherwise */
+                               childidxs = RelationGetIndexList(childrel);
+
+                               foreach(cell, childidxs)
+                               {
+                                       Oid                     cldidxid = 
lfirst_oid(cell);
+                                       Relation        cldidx;
+                                       IndexInfo  *cldIdxInfo;
+
+                                       cldidx = index_open(cldidxid, 
AccessShareLock);
+
+                                       /* this index is already partition of 
another one */
+                                       if (cldidx->rd_index->indparentidx != 0)
+                                       {
+                                               index_close(cldidx, 
AccessShareLock);
+                                               continue;
+                                       }
+
+                                       cldIdxInfo = BuildIndexInfo(cldidx);
+                                       if (attmap == NULL)
+                                               attmap =
+                                                       
convert_tuples_by_name_map(RelationGetDescr(childrel),
+                                                                               
                           parentDesc,
+                                                                               
                           gettext_noop("could not convert row type"));
+
+                                       if (CompareIndexInfo(cldIdxInfo, 
indexInfo, attmap))
+                                       {
+                                               /* matched */
+                                               IndexSetParentIndex(cldidx, 
indexRelationId);
+                                               found = true;
+                                               index_close(cldidx, 
AccessShareLock);
+                                               break;
+                                       }
+
+                                       index_close(cldidx, AccessShareLock);
+                               }
+
+                               heap_close(childrel, NoLock);
+
+                               if (!found)
+                               {
+                                       IndexStmt *childStmt;
+
+                                       childStmt = copyObject(stmt);
+                                       childStmt->idxname = NULL;
+                                       childStmt->relationId = childRelid;
+
+                                       /*
+                                        * Note that the event trigger command 
stash will only contain
+                                        * a command to create the top-level 
index, not each of the
+                                        * individual index partitions.
+                                        */
+                                       /* XXX emit a DDL event here? */
+                                       DefineIndex(childRelid, childStmt,
+                                                               InvalidOid,     
                /* no predefined OID */
+                                                               
indexRelationId,        /* this is our child */
+                                                               false, 
check_rights, check_not_in_use,
+                                                               false, quiet);
+                               }
+                       }
+               }
+               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 */
@@ -1762,7 +1944,7 @@ ChooseIndexColumnNames(List *indexElems)
  * ReindexIndex
  *             Recreate a specific index.
  */
-Oid
+void
 ReindexIndex(RangeVar *indexRelation, int options)
 {
        Oid                     indOid;
@@ -1785,12 +1967,17 @@ ReindexIndex(RangeVar *indexRelation, int options)
         * 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;
 }
 
 /*
@@ -1829,7 +2016,8 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
        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)));
@@ -1973,6 +2161,12 @@ ReindexMultipleTables(const char *objectName, 
ReindexObjectType objectKind,
                /*
                 * Only regular tables and matviews can have indexes, so ignore 
any
                 * other kind of relation.
+                *
+                * XXX 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)
@@ -2035,3 +2229,67 @@ ReindexMultipleTables(const char *objectName, 
ReindexObjectType objectKind,
 
        MemoryContextDelete(private_context);
 }
+
+/*
+ * Reindex each child of a partitioned index.
+ *
+ * The parent index is given, locked in AccessExclusive mode; this routine
+ * obtains the list of children and releases the lock on parent before
+ * applying reindex on each child.
+ */
+static void
+ReindexPartitionedIndex(Relation parentIdx)
+{
+       ereport(ERROR,
+                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                        errmsg("REINDEX is not yet implemented for partitioned 
indexes")));
+}
+
+/*
+ * Update the pg_index tuple corresponding to the given index on a partition
+ * to indicate that the given index OID is now its parent partitioned index.
+ *
+ * (De-)register the dependency from/in pg_depend.
+ */
+void
+IndexSetParentIndex(Relation idx, Oid parentOid)
+{
+       Relation        pgindex;
+       HeapTuple       indTup;
+       Form_pg_index indForm;
+
+       /* Make sure this is an index */
+       Assert(idx->rd_rel->relkind == RELKIND_INDEX ||
+                  idx->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
+
+       pgindex = heap_open(IndexRelationId, RowExclusiveLock);
+       indTup = idx->rd_indextuple;
+       indForm = (Form_pg_index) GETSTRUCT(indTup);
+       indForm->indparentidx = parentOid;
+
+       CatalogTupleUpdate(pgindex, &(indTup->t_self), indTup);
+
+       heap_close(pgindex, RowExclusiveLock);
+
+       /*
+        * If setting a parent, add a pg_depend row; if making standalone, 
remove
+        * all existing rows.
+        */
+       if (OidIsValid(parentOid))
+       {
+               ObjectAddress   parent;
+               ObjectAddress   partition;
+
+               /* set up pg_depend */
+               ObjectAddressSet(parent, RelationRelationId, parentOid);
+               ObjectAddressSet(partition, RelationRelationId, 
RelationGetRelid(idx));
+               recordDependencyOn(&partition, &parent, DEPENDENCY_INTERNAL);
+       }
+       else
+       {
+               deleteDependencyRecordsForClass(RelationRelationId,
+                                                                               
RelationGetRelid(idx),
+                                                                               
RelationRelationId,
+                                                                               
DEPENDENCY_INTERNAL);
+       }
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d19846d005..6b127aa954 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -266,6 +266,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
                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}
 };
 
@@ -481,6 +487,9 @@ static void ValidatePartitionConstraints(List **wqueue, 
Relation scanrel,
                                                         List *partConstraint,
                                                         bool validate_default);
 static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
+static ObjectAddress ATExecAttachPartitionIdx(List **wqueue, Relation rel,
+                                                RangeVar *name);
+static ObjectAddress ATExecDetachPartitionIdx(Relation rel, RangeVar *name);
 
 
 /* ----------------------------------------------------------------
@@ -901,6 +910,52 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
        }
 
        /*
+        * Now that the partition spec is installed, we can create any indexes
+        * from the partitioned table into the partitions.  We can't do it
+        * earlier, because if this is a partition which in turn is partitioned,
+        * DefineIndex would not be able to recurse correctly into children to
+        * apply indexes from the parent table.
+        */
+       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);
+       }
+
+       /*
         * Now add any newly specified column default values and CHECK 
constraints
         * to the new relation.  These are passed to us in the form of raw
         * parsetrees; we need to transform them to executable expression trees
@@ -1180,10 +1235,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, 
Oid relOid, Oid oldRelOid,
         * 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.  There's an equivalent
+        * problem 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;
 
@@ -1211,7 +1269,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid 
relOid, Oid oldRelOid,
         * 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))
@@ -2541,6 +2600,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, 
bool recursing)
                relkind != RELKIND_MATVIEW &&
                relkind != RELKIND_COMPOSITE_TYPE &&
                relkind != RELKIND_INDEX &&
+               relkind != RELKIND_PARTITIONED_INDEX &&
                relkind != RELKIND_FOREIGN_TABLE &&
                relkind != RELKIND_PARTITIONED_TABLE)
                ereport(ERROR,
@@ -3020,7 +3080,8 @@ RenameRelationInternal(Oid myrelid, const char 
*newrelname, bool is_internal)
        /*
         * 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);
 
@@ -3074,6 +3135,7 @@ CheckTableNotInUse(Relation rel, const char *stmt)
                                                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),
@@ -3766,7 +3828,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
                        break;
                case AT_AttachPartition:
                case AT_DetachPartition:
-                       ATSimplePermissions(rel, ATT_TABLE);
+                       ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX);
                        /* No command-specific prep needed */
                        pass = AT_PASS_MISC;
                        break;
@@ -4113,10 +4175,18 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, 
Relation rel,
                        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:
-                       ATExecDetachPartition(rel, ((PartitionCmd *) 
cmd->def)->name);
+                       if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+                               ATExecDetachPartition(rel, ((PartitionCmd *) 
cmd->def)->name);
+                       else
+                               ATExecDetachPartitionIdx(rel,
+                                                                               
 ((PartitionCmd *) cmd->def)->name);
                        break;
                default:                                /* oops */
                        elog(ERROR, "unrecognized alter table type: %d",
@@ -4751,6 +4821,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
                        actual_target = ATT_MATVIEW;
                        break;
                case RELKIND_INDEX:
+               case RELKIND_PARTITIONED_INDEX:
                        actual_target = ATT_INDEX;
                        break;
                case RELKIND_COMPOSITE_TYPE:
@@ -6195,6 +6266,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, 
int16 colNum, Node *newVa
        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,
@@ -6206,7 +6278,9 @@ ATPrepSetStatistics(Relation rel, const char *colName, 
int16 colNum, Node *newVa
         * 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")));
@@ -6284,7 +6358,8 @@ ATExecSetStatistics(Relation rel, const char *colName, 
int16 colNum, Node *newVa
                                 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),
@@ -6797,6 +6872,7 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
        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 */
@@ -9198,7 +9274,9 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
                                {
                                        char            relKind = 
get_rel_relkind(foundObject.objectId);
 
-                                       if (relKind == RELKIND_INDEX)
+                                       /* XXX does this do the right thing?  
Probably not */
+                                       if (relKind == RELKIND_INDEX ||
+                                               relKind == 
RELKIND_PARTITIONED_INDEX)
                                        {
                                                Assert(foundObject.objectSubId 
== 0);
                                                if 
(!list_member_oid(tab->changedIndexOids, foundObject.objectId))
@@ -10162,6 +10240,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool 
recursing, LOCKMODE lock
                 */
                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);
@@ -10169,7 +10248,8 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool 
recursing, LOCKMODE lock
                /*
                 * 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);
 
                /*
@@ -10194,6 +10274,15 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, 
bool recursing, LOCKMODE lock
                        list_free(index_oid_list);
                }
 
+#if 0
+               /* For partitioned indexes, recurse to each child index */
+               if (tuple_class->relkind == RELKIND_PARTITIONED_INDEX)
+               {
+                       foreach (i, child_index_oid)
+                               ATExecChangeOwner(lfirst_oid(i), newOwnerId, 
true, lockmode);
+               }
+#endif
+
                if (tuple_class->relkind == RELKIND_RELATION ||
                        tuple_class->relkind == RELKIND_MATVIEW)
                {
@@ -10486,6 +10575,7 @@ ATExecSetRelOptions(Relation rel, List *defList, 
AlterTableType operation,
                        (void) view_reloptions(newOptions, true);
                        break;
                case RELKIND_INDEX:
+               case RELKIND_PARTITIONED_INDEX:
                        (void) index_reloptions(rel->rd_amroutine->amoptions, 
newOptions, true);
                        break;
                default:
@@ -10898,7 +10988,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
                         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;
@@ -13285,7 +13376,8 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, 
Oid relid, Oid oldrelid,
                                (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),
@@ -14006,6 +14098,103 @@ ATExecAttachPartition(List **wqueue, Relation rel, 
PartitionCmd *cmd)
        StorePartitionBound(attachrel, rel, cmd->bound);
 
        /*
+        * Ensure a correct set of indexes in the partition.  This either 
creates
+        * a new index in the table being attached, or re-parents an existing 
one.
+        */
+       {
+               AttrNumber *attmap = NULL;
+               List       *idxes;
+               List       *attachRelIdxs;
+               Relation   *attachrelIdxRels;
+               IndexInfo **attachInfos;
+               int                     i;
+               ListCell   *cell;
+
+               idxes = RelationGetIndexList(rel);
+               attachRelIdxs = RelationGetIndexList(attachrel);
+               attachrelIdxRels = palloc(sizeof(Relation) * 
list_length(attachRelIdxs));
+               attachInfos = palloc(sizeof(IndexInfo *) * 
list_length(attachRelIdxs));
+
+               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 match in the 
table
+                * being attached as partition; if one is not found, create one.
+                */
+               foreach(cell, idxes)
+               {
+                       Oid                     idx = lfirst_oid(cell);
+                       Relation        idxRel = index_open(idx, 
AccessShareLock);
+                       IndexInfo  *info;
+                       bool            found = false;
+
+                       if (idxRel->rd_rel->relkind != 
RELKIND_PARTITIONED_INDEX)
+                       {
+                               index_close(idxRel, AccessShareLock);
+                               continue;
+                       }
+                       info = BuildIndexInfo(idxRel);
+                       if (attmap == NULL)
+                               attmap =
+                                       
convert_tuples_by_name_map(RelationGetDescr(attachrel),
+                                                                               
           RelationGetDescr(rel),
+                                                                               
           gettext_noop("could not convert row type"));
+
+                       for (i = 0; i < list_length(attachRelIdxs); i++)
+                       {
+                               /* already used it */
+                               if (attachrelIdxRels[i]->rd_index->indparentidx 
!= 0)
+                                       continue;
+
+                               if (CompareIndexInfo(info, attachInfos[i], 
attmap))
+                               {
+                                       /* bingo. */
+                                       
IndexSetParentIndex(attachrelIdxRels[i], idx);
+                                       found = true;
+                                       break;
+                               }
+                       }
+                       if (!found)
+                       {
+                               IndexStmt  *stmt;
+
+                               stmt = generateClonedIndexStmt(NULL, 
RelationGetRelid(attachrel),
+                                                                               
           idxRel, attmap,
+                                                                               
           RelationGetDescr(rel)->natts);
+                               /* XXX emit a DDL event here */
+                               DefineIndex(RelationGetRelid(attachrel), stmt, 
InvalidOid,
+                                                       
RelationGetRelid(idxRel),
+                                                       false, false, false, 
false, false);
+                       }
+
+                       index_close(idxRel, AccessShareLock);
+               }
+
+               /* Clean up. */
+               if (attmap)
+                       pfree(attmap);
+
+               for (i = 0; i < list_length(attachRelIdxs); i++)
+               {
+                       pfree(attachInfos[i]);
+                       index_close(attachrelIdxRels[i], AccessShareLock);
+               }
+
+               if (idxes)
+                       pfree(idxes);
+               if (attachRelIdxs)
+                       pfree(attachRelIdxs);
+       }
+
+       /*
         * Generate partition constraint from the partition bound specification.
         * If the parent itself is a partition, make sure to include its
         * constraint as well.
@@ -14092,6 +14281,8 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
                                new_repl[Natts_pg_class];
        ObjectAddress address;
        Oid                     defaultPartOid;
+       List       *indexes;
+       ListCell   *cell;
 
        /*
         * We must lock the default partition, because detaching this partition
@@ -14150,6 +14341,25 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
                }
        }
 
+       /* detach indexes too */
+       indexes = RelationGetIndexList(partRel);
+       foreach(cell, indexes)
+       {
+               Oid                     idxid = lfirst_oid(cell);
+               Relation        idx = index_open(idxid, AccessExclusiveLock);
+
+               if (idx->rd_index->indparentidx != InvalidOid)
+               {
+                       Assert(IndexGetRelation(idx->rd_index->indparentidx, 
false) ==
+                                  RelationGetRelid(rel));
+
+                       IndexSetParentIndex(idx, InvalidOid);
+               }
+
+               relation_close(idx, AccessExclusiveLock);
+       }
+
+
        /*
         * Invalidate the parent's relcache so that the partition is no longer
         * included in its partition descriptor.
@@ -14163,3 +14373,133 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
 
        return address;
 }
+
+/*
+ * ALTER INDEX i1 ATTACH PARTITION i2
+ */
+static ObjectAddress
+ATExecAttachPartitionIdx(List **wqueue, Relation rel, RangeVar *name)
+{
+       Relation        partRel;
+       ObjectAddress address;
+
+       /* XXX do we need this strong a lock? */
+       partRel = relation_openrv(name, AccessExclusiveLock);
+
+       /* Ensure it's what we need */
+       if (partRel->rd_rel->relkind != RELKIND_INDEX &&
+               partRel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                errmsg("\"%s\" is not an index", 
RelationGetRelationName(partRel))));
+
+       ObjectAddressSet(address, RelationRelationId, 
RelationGetRelid(partRel));
+
+       /* Silently do nothing if already the right state */
+       if (partRel->rd_index->indparentidx != RelationGetRelid(rel))
+       {
+               Relation        parentTable,
+                                       partTable;
+               IndexInfo  *childInfo;
+               IndexInfo  *parentInfo;
+               AttrNumber *attmap;
+               bool            found;
+               int                     i;
+               PartitionDesc partDesc;
+
+               if (OidIsValid(partRel->rd_index->indparentidx))
+                       ereport(ERROR,
+                                       
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                        errmsg("index \"%s\" is already a 
partition of another index",
+                                                       
RelationGetRelationName(partRel))));
+
+               /* Make sure it indexes a partition of the other index's table 
*/
+               /* XXX need to avoid deadlocks here somehow */
+               parentTable = heap_open(rel->rd_index->indrelid, 
AccessShareLock);
+               partTable = heap_open(partRel->rd_index->indrelid, 
AccessShareLock);
+
+               partDesc = RelationGetPartitionDesc(parentTable);
+               found = false;
+               for (i = 0; i < partDesc->nparts; i++)
+               {
+                       if (partDesc->oids[i] == RelationGetRelid(partTable))
+                       {
+                               found = true;
+                               break;
+                       }
+               }
+               if (!found)
+                       ereport(ERROR,
+                                       (errmsg("index \"%s\" is not on a 
partition of table \"%s\"",
+                                                       
RelationGetRelationName(partRel),
+                                                       
RelationGetRelationName(parentTable))));
+
+               /*
+                * Make sure the indexes are compatible.  It seems like this 
could be
+                * relaxed a bit: for instance maybe it'd be useful to have a 
hash index
+                * on one partition and a btree index in another.  But let's 
keep this
+                * simple for now.
+                */
+               childInfo = BuildIndexInfo(partRel);
+               parentInfo = BuildIndexInfo(rel);
+               /* XXX are these inverted? */
+               attmap = convert_tuples_by_name_map(RelationGetDescr(partTable),
+                                                                               
        RelationGetDescr(parentTable),
+                                                                               
        gettext_noop("could not convert row type"));
+               if (!CompareIndexInfo(parentInfo, childInfo, attmap))
+                       ereport(ERROR,
+                                       
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                        errmsg("definition of index \"%s\" is 
not compatible with index \"%s\"",
+                                                       
RelationGetRelationName(partRel),
+                                                       
RelationGetRelationName(rel))));
+
+               /* All good -- do it */
+               IndexSetParentIndex(partRel, RelationGetRelid(rel));
+
+               relation_close(parentTable, AccessShareLock);
+               relation_close(partTable, AccessShareLock);
+       }
+
+       relation_close(partRel, AccessExclusiveLock);
+
+       return address;
+}
+
+/*
+ * ALTER INDEX i1 DETACH INDEX i2
+ */
+static ObjectAddress
+ATExecDetachPartitionIdx(Relation rel, RangeVar *name)
+{
+       Relation        partRel;
+       ObjectAddress address;
+
+       /* XXX do we need this strong a lock? */
+       partRel = relation_openrv(name, AccessExclusiveLock);
+
+       /* Ensure it's what we need */
+       if (partRel->rd_rel->relkind != RELKIND_INDEX &&
+               partRel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                errmsg("\"%s\" is not an index", 
RelationGetRelationName(partRel))));
+
+       /* Silently do nothing if already the right state */
+       if (OidIsValid(partRel->rd_index->indparentidx))
+       {
+               if (partRel->rd_index->indparentidx != RelationGetRelid(rel))
+                       ereport(ERROR,
+                                       
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                                        errmsg("\"%s\" is not attached to 
\"%s\"",
+                                                       
RelationGetRelationName(partRel),
+                                                       
RelationGetRelationName(rel))));
+
+               /* all good */
+               IndexSetParentIndex(partRel, InvalidOid);
+       }
+
+       ObjectAddressSet(address, RelationRelationId, 
RelationGetRelid(partRel));
+       relation_close(partRel, AccessExclusiveLock);
+
+       return address;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 78cbf8029c..2608396cae 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -290,7 +290,7 @@ static Node *makeRecursiveViewSelect(char *relname, List 
*aliases, Node *query);
 %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
@@ -1876,6 +1876,15 @@ AlterTableStmt:
                                        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 =
@@ -2010,6 +2019,35 @@ partition_cmd:
                                }
                ;
 
+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 INDEX <name> DETACH PARTITION <index_name> */
+                       | DETACH PARTITION qualified_name
+                               {
+                                       AlterTableCmd *n = 
makeNode(AlterTableCmd);
+                                       PartitionCmd *cmd = 
makeNode(PartitionCmd);
+
+                                       n->subtype = AT_DetachPartition;
+                                       cmd->name = $3;
+                                       cmd->bound = NULL;
+                                       n->def = (Node *) cmd;
+
+                                       $$ = (Node *) n;
+                               }
+                       ;
+
 alter_table_cmd:
                        /* ALTER TABLE <name> ADD <coldef> */
                        ADD_P columnDef
@@ -7228,7 +7266,7 @@ defacl_privilege_target:
  *****************************************************************************/
 
 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);
@@ -7255,7 +7293,7 @@ IndexStmt:        CREATE opt_unique INDEX 
opt_concurrently opt_index_name
                                        $$ = (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);
diff --git a/src/backend/parser/parse_utilcmd.c 
b/src/backend/parser/parse_utilcmd.c
index f9e8f6da0d..938e73f8ad 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3279,18 +3279,39 @@ transformPartitionCmd(CreateStmtContext *cxt, 
PartitionCmd *cmd)
 {
        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;
+       }
 }
 
 /*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 82a707af7b..93c09f73a8 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -23,6 +23,7 @@
 #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"
@@ -1296,6 +1297,7 @@ ProcessUtilitySlow(ParseState *pstate,
                                        IndexStmt  *stmt = (IndexStmt *) 
parsetree;
                                        Oid                     relid;
                                        LOCKMODE        lockmode;
+                                       List       *inheritors = NIL;
 
                                        if (stmt->concurrent)
                                                
PreventTransactionChain(isTopLevel,
@@ -1317,6 +1319,9 @@ ProcessUtilitySlow(ParseState *pstate,
                                                                                
                 false, false,
                                                                                
                 RangeVarCallbackOwnsRelation,
                                                                                
                 NULL);
+                                       /* Also, lock any descendant tables if 
recursive */
+                                       if (stmt->relation->inh)
+                                               inheritors = 
find_all_inheritors(relid, lockmode, NULL);
 
                                        /* Run parse analysis ... */
                                        stmt = transformIndexStmt(relid, stmt, 
queryString);
@@ -1327,6 +1332,7 @@ ProcessUtilitySlow(ParseState *pstate,
                                                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 */
@@ -1342,6 +1348,9 @@ ProcessUtilitySlow(ParseState *pstate,
                                                                                
                         parsetree);
                                        commandCollected = true;
                                        EventTriggerAlterTableEnd();
+
+                                       if (inheritors)
+                                               list_free(inheritors);
                                }
                                break;
 
diff --git a/src/backend/utils/adt/amutils.c b/src/backend/utils/adt/amutils.c
index f53b251b30..6af53402ce 100644
--- a/src/backend/utils/adt/amutils.c
+++ b/src/backend/utils/adt/amutils.c
@@ -183,7 +183,8 @@ indexam_property(FunctionCallInfo fcinfo,
                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();
diff --git a/src/backend/utils/adt/ruleutils.c 
b/src/backend/utils/adt/ruleutils.c
index 06cf32f5d7..0aaeb0f884 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -1259,9 +1259,11 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
        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 ?
+                                                        "ONLY " : "",
                                                         
generate_relation_name(indrelid, NIL),
                                                         
quote_identifier(NameStr(amrec->amname)));
                else                                    /* currently, must be 
EXCLUDE constraint */
diff --git a/src/backend/utils/cache/relcache.c 
b/src/backend/utils/cache/relcache.c
index 59280cd8bb..bddcbfc54f 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -430,6 +430,7 @@ static void
 RelationParseRelOptions(Relation relation, HeapTuple tuple)
 {
        bytea      *options;
+       bool            isindex;
 
        relation->rd_options = NULL;
 
@@ -439,6 +440,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
                case RELKIND_RELATION:
                case RELKIND_TOASTVALUE:
                case RELKIND_INDEX:
+               case RELKIND_PARTITIONED_INDEX:
                case RELKIND_VIEW:
                case RELKIND_MATVIEW:
                case RELKIND_PARTITIONED_TABLE:
@@ -452,10 +454,12 @@ RelationParseRelOptions(Relation relation, HeapTuple 
tuple)
         * we might not have any other for pg_class yet (consider executing this
         * code for pg_class itself)
         */
+       isindex = relation->rd_rel->relkind == RELKIND_INDEX ||
+               relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX;
        options = extractRelOptions(tuple,
                                                                
GetPgClassDescriptor(),
-                                                               
relation->rd_rel->relkind == RELKIND_INDEX ?
-                                                               
relation->rd_amroutine->amoptions : NULL);
+                                                               isindex ? 
relation->rd_amroutine->amoptions :
+                                                               NULL);
 
        /*
         * Copy parsed data into CacheMemoryContext.  To guard against the
@@ -2045,7 +2049,8 @@ RelationIdGetRelation(Oid relationId)
                         * 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);
@@ -2159,7 +2164,8 @@ RelationReloadIndexInfo(Relation relation)
        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 */
@@ -2379,7 +2385,8 @@ RelationClearRelation(Relation relation, bool rebuild)
        {
                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())
@@ -2395,7 +2402,8 @@ RelationClearRelation(Relation relation, bool rebuild)
         * 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)
        {
@@ -5453,7 +5461,10 @@ load_relcache_init_file(bool shared)
                        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;
@@ -5815,7 +5826,10 @@ write_relcache_init_file(bool shared)
                                   (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 */
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 4b47951de1..7c379b8110 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -68,6 +68,7 @@ static int    numextmembers;
 
 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);
@@ -76,6 +77,8 @@ static int    ExtensionMemberIdCompare(const void *p1, const 
void *p2);
 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);
 
 
 /*
@@ -258,6 +261,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
        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);
 
@@ -354,6 +361,57 @@ flagInhTables(Archive *fout, TableInfo *tblinfo, int 
numTables,
        }
 }
 
+/*
+ * flagInhIndexes -
+ *     Fill in each partitioned index's ->parentidx pointer.
+ */
+static void
+flagInhIndexes(Archive *fout, TableInfo *tblinfo, int numTables)
+{
+       int             i,
+                       j;
+       DumpableObject ***parentIndexArray;
+
+       parentIndexArray = (DumpableObject ***)
+               pg_malloc0(getMaxDumpId() * sizeof(DumpableObject **));
+
+       for (i = 0; i < numTables; i++)
+       {
+               TableInfo          *parenttbl;
+
+               if (!tblinfo[i].ispartition || tblinfo[i].numParents == 0)
+                       continue;
+
+               Assert(tblinfo[i].numParents == 1);
+               parenttbl = tblinfo[i].parents[0];
+
+               if (parentIndexArray[parenttbl->dobj.dumpId] == NULL)
+                       parentIndexArray[parenttbl->dobj.dumpId] =
+                               buildIndexArray(parenttbl->indexes,
+                                                               
parenttbl->numIndexes,
+                                                               
sizeof(IndxInfo));
+
+               for (j = 0; j < tblinfo[i].numIndexes; j++)
+               {
+                       IndxInfo  *index = &(tblinfo[i].indexes[j]);
+
+                       if (index->indparentidx == 0)
+                               continue;
+
+                       index->parentidx =
+                               findIndexByOid(index->indparentidx,
+                                                          
parentIndexArray[parenttbl->dobj.dumpId],
+                                                          
parenttbl->numIndexes);
+               }
+       }
+
+       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
  *
@@ -827,6 +885,18 @@ findExtensionByOid(Oid oid)
        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
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d8fb356130..458fd5657d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -6509,6 +6509,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int 
numTables)
        int                     i_tableoid,
                                i_oid,
                                i_indexname,
+                               i_parentidx,
                                i_indexdef,
                                i_indnkeys,
                                i_indkey,
@@ -6530,10 +6531,6 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int 
numTables)
        {
                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;
 
@@ -6561,7 +6558,35 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int 
numTables)
                 * is not.
                 */
                resetPQExpBuffer(query);
-               if (fout->remoteVersion >= 90400)
+               if (fout->remoteVersion >= 11000)
+               {
+                       appendPQExpBuffer(query,
+                                                         "SELECT t.tableoid, 
t.oid, "
+                                                         "t.relname AS 
indexname, "
+                                                         "i.indparentidx, "
+                                                         
"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) "
+                                                         "LEFT JOIN 
pg_catalog.pg_constraint c "
+                                                         "ON (i.indrelid = 
c.conrelid AND "
+                                                         "i.indexrelid = 
c.conindid AND "
+                                                         "c.contype IN 
('p','u','x')) "
+                                                         "WHERE i.indrelid = 
'%u'::pg_catalog.oid "
+                                                         "AND i.indisvalid 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
@@ -6570,6 +6595,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int 
numTables)
                        appendPQExpBuffer(query,
                                                          "SELECT t.tableoid, 
t.oid, "
                                                          "t.relname AS 
indexname, "
+                                                         "0 AS indparentidx, "
                                                          
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
                                                          "t.relnatts AS 
indnkeys, "
                                                          "i.indkey, 
i.indisclustered, "
@@ -6601,6 +6627,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int 
numTables)
                        appendPQExpBuffer(query,
                                                          "SELECT t.tableoid, 
t.oid, "
                                                          "t.relname AS 
indexname, "
+                                                         "0 AS indparentidx, "
                                                          
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
                                                          "t.relnatts AS 
indnkeys, "
                                                          "i.indkey, 
i.indisclustered, "
@@ -6628,6 +6655,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int 
numTables)
                        appendPQExpBuffer(query,
                                                          "SELECT t.tableoid, 
t.oid, "
                                                          "t.relname AS 
indexname, "
+                                                         "0 AS indparentidx, "
                                                          
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
                                                          "t.relnatts AS 
indnkeys, "
                                                          "i.indkey, 
i.indisclustered, "
@@ -6658,6 +6686,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int 
numTables)
                        appendPQExpBuffer(query,
                                                          "SELECT t.tableoid, 
t.oid, "
                                                          "t.relname AS 
indexname, "
+                                                         "0 AS indparentidx, "
                                                          
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
                                                          "t.relnatts AS 
indnkeys, "
                                                          "i.indkey, 
i.indisclustered, "
@@ -6690,6 +6719,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int 
numTables)
                i_tableoid = PQfnumber(res, "tableoid");
                i_oid = PQfnumber(res, "oid");
                i_indexname = PQfnumber(res, "indexname");
+               i_parentidx = PQfnumber(res, "indparentidx");
                i_indexdef = PQfnumber(res, "indexdef");
                i_indnkeys = PQfnumber(res, "indnkeys");
                i_indkey = PQfnumber(res, "indkey");
@@ -6706,8 +6736,10 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int 
numTables)
                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++)
                {
@@ -6729,6 +6761,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int 
numTables)
                                                  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].indparentidx = atooid(PQgetvalue(res, j, 
i_parentidx));
+                       indxinfo[j].parentidx = NULL;   /* set in 
flagInhIndexes */
                        indxinfo[j].relpages = atoi(PQgetvalue(res, j, 
i_relpages));
                        contype = *(PQgetvalue(res, j, i_contype));
 
@@ -16123,6 +16157,17 @@ dumpIndex(Archive *fout, IndxInfo *indxinfo)
                                                          
fmtId(indxinfo->dobj.name));
                }
 
+               /* If the index is a partition, attach it to its parent */
+               if (indxinfo->parentidx)
+               {
+                       appendPQExpBuffer(q, "\nALTER INDEX %s ",
+                                                         
fmtQualifiedId(fout->remoteVersion,
+                                                                               
         indxinfo->parentidx->dobj.namespace->dobj.name,
+                                                                               
         indxinfo->parentidx->dobj.name));
+                       appendPQExpBuffer(q, "ATTACH PARTITION %s;\n",
+                                                         
fmtId(indxinfo->dobj.name));
+               }
+
                /*
                 * DROP must be fully qualified in case same name appears in
                 * pg_catalog
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index da884ffd09..cdeda0a840 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -328,6 +328,8 @@ typedef struct _tableInfo
         */
        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 */
@@ -361,6 +363,8 @@ typedef struct _indxInfo
        Oid                *indkeys;
        bool            indisclustered;
        bool            indisreplident;
+       Oid                     indparentidx;   /* if partitioned, parent index 
OID */
+       struct _indxInfo *parentidx;    /* link to parent index (initially 
NULL) */
        /* if there is an associated constraint object, its dumpId: */
        DumpId          indexconstraint;
        int                     relpages;               /* relpages of the 
underlying table */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index b7b978a361..45502fe07c 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1703,7 +1703,8 @@ describeOneTableDetails(const char *schemaname,
                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");
@@ -1764,6 +1765,7 @@ describeOneTableDetails(const char *schemaname,
                                                                  schemaname, 
relationname);
                        break;
                case RELKIND_INDEX:
+               case RELKIND_PARTITIONED_INDEX:
                        if (tableinfo.relpersistence == 'u')
                                printfPQExpBuffer(&title, _("Unlogged index 
\"%s.%s\""),
                                                                  schemaname, 
relationname);
@@ -1821,7 +1823,8 @@ describeOneTableDetails(const char *schemaname,
                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)
@@ -1832,6 +1835,7 @@ describeOneTableDetails(const char *schemaname,
                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)
@@ -1904,7 +1908,8 @@ describeOneTableDetails(const char *schemaname,
                }
 
                /* 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 
*/
@@ -1928,6 +1933,7 @@ describeOneTableDetails(const char *schemaname,
                        /* 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)
@@ -2019,7 +2025,8 @@ describeOneTableDetails(const char *schemaname,
                PQclear(result);
        }
 
-       if (tableinfo.relkind == RELKIND_INDEX)
+       if (tableinfo.relkind == RELKIND_INDEX ||
+               tableinfo.relkind == RELKIND_PARTITIONED_INDEX)
        {
                /* Footer information about an index */
                PGresult   *result;
@@ -3372,6 +3379,7 @@ listTables(const char *tabtypes, const char *pattern, 
bool verbose, bool showSys
                                          " 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"),
@@ -3384,6 +3392,7 @@ listTables(const char *tabtypes, const char *pattern, 
bool verbose, bool showSys
                                          gettext_noop("special"),
                                          gettext_noop("foreign table"),
                                          gettext_noop("table"),        /* 
partitioned table */
+                                         gettext_noop("index"),        /* 
partitioned index */
                                          gettext_noop("Type"),
                                          gettext_noop("Owner"));
 
@@ -3429,7 +3438,8 @@ listTables(const char *tabtypes, const char *pattern, 
bool verbose, bool showSys
        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)
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index bd4014a69d..b608dac156 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201711092
+#define CATALOG_VERSION_NO     201711101
 
 #endif
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index ceaa91f1b2..36245c96da 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -47,10 +47,12 @@ extern void index_check_primary_key(Relation heapRel,
 #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)
 
 extern Oid index_create(Relation heapRelation,
                         const char *indexRelationName,
                         Oid indexRelationId,
+                        Oid parentIndexRelid,
                         Oid relFileNode,
                         IndexInfo *indexInfo,
                         List *indexColNames,
@@ -84,6 +86,8 @@ extern void index_drop(Oid indexId, bool concurrent);
 
 extern IndexInfo *BuildIndexInfo(Relation index);
 
+extern bool CompareIndexInfo(IndexInfo *info1, IndexInfo *info2, AttrNumber 
*attmap);
+
 extern void BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii);
 
 extern void FormIndexDatum(IndexInfo *indexInfo,
@@ -134,4 +138,6 @@ extern bool ReindexIsProcessingHeap(Oid heapOid);
 extern bool ReindexIsProcessingIndex(Oid indexOid);
 extern Oid     IndexGetRelation(Oid indexId, bool missing_ok);
 
+extern void IndexSetParentIndex(Relation idx, Oid parentOid);
+
 #endif                                                 /* INDEX_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index b256657bda..dd8e7ea2b5 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -166,6 +166,7 @@ DESCR("");
 #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 */
diff --git a/src/include/catalog/pg_index.h b/src/include/catalog/pg_index.h
index 8505c3be5f..e7afb0b921 100644
--- a/src/include/catalog/pg_index.h
+++ b/src/include/catalog/pg_index.h
@@ -32,6 +32,7 @@ CATALOG(pg_index,2610) BKI_WITHOUT_OIDS BKI_SCHEMA_MACRO
 {
        Oid                     indexrelid;             /* OID of the index */
        Oid                     indrelid;               /* OID of the relation 
it indexes */
+       Oid                     indparentidx;   /* OID of parent index, 0 if 
not partitioned */
        int16           indnatts;               /* number of columns in index */
        bool            indisunique;    /* is this a unique index? */
        bool            indisprimary;   /* is this index for primary key? */
@@ -70,26 +71,27 @@ typedef FormData_pg_index *Form_pg_index;
  *             compiler constants for pg_index
  * ----------------
  */
-#define Natts_pg_index                                 19
+#define Natts_pg_index                                 20
 #define Anum_pg_index_indexrelid               1
 #define Anum_pg_index_indrelid                 2
-#define Anum_pg_index_indnatts                 3
-#define Anum_pg_index_indisunique              4
-#define Anum_pg_index_indisprimary             5
-#define Anum_pg_index_indisexclusion   6
-#define Anum_pg_index_indimmediate             7
-#define Anum_pg_index_indisclustered   8
-#define Anum_pg_index_indisvalid               9
-#define Anum_pg_index_indcheckxmin             10
-#define Anum_pg_index_indisready               11
-#define Anum_pg_index_indislive                        12
-#define Anum_pg_index_indisreplident   13
-#define Anum_pg_index_indkey                   14
-#define Anum_pg_index_indcollation             15
-#define Anum_pg_index_indclass                 16
-#define Anum_pg_index_indoption                        17
-#define Anum_pg_index_indexprs                 18
-#define Anum_pg_index_indpred                  19
+#define Anum_pg_index_indparentidx             3
+#define Anum_pg_index_indnatts                 4
+#define Anum_pg_index_indisunique              5
+#define Anum_pg_index_indisprimary             6
+#define Anum_pg_index_indisexclusion   7
+#define Anum_pg_index_indimmediate             8
+#define Anum_pg_index_indisclustered   9
+#define Anum_pg_index_indisvalid               10
+#define Anum_pg_index_indcheckxmin             11
+#define Anum_pg_index_indisready               12
+#define Anum_pg_index_indislive                        13
+#define Anum_pg_index_indisreplident   14
+#define Anum_pg_index_indkey                   15
+#define Anum_pg_index_indcollation             16
+#define Anum_pg_index_indclass                 17
+#define Anum_pg_index_indoption                        18
+#define Anum_pg_index_indexprs                 19
+#define Anum_pg_index_indpred                  20
 
 /*
  * Index AMs that support ordered scans must support these two indoption
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index bfead9af3d..fd2978f470 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -25,12 +25,13 @@ extern void RemoveObjects(DropStmt *stmt);
 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);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b4ce3548dc..6751d7e0c0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -839,7 +839,7 @@ typedef struct PartitionRangeDatum
 } PartitionRangeDatum;
 
 /*
- * PartitionCmd - info for ALTER TABLE ATTACH/DETACH PARTITION commands
+ * PartitionCmd - info for ALTER TABLE/INDEX ATTACH/DETACH PARTITION commands
  */
 typedef struct PartitionCmd
 {
@@ -2700,7 +2700,8 @@ typedef struct FetchStmt
  * properties are empty.
  *
  * The relation to build the index on can be represented either by name
- * or by OID.
+ * (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
diff --git a/src/test/regress/expected/alter_table.out 
b/src/test/regress/expected/alter_table.out
index 11f0baa11b..5a96b0b5e1 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3276,7 +3276,7 @@ CREATE TABLE unparted (
 );
 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 (
@@ -3656,7 +3656,7 @@ DROP TABLE fail_part;
 -- 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;
diff --git a/src/test/regress/expected/indexing.out 
b/src/test/regress/expected/indexing.out
new file mode 100644
index 0000000000..cde29bebb5
--- /dev/null
+++ b/src/test/regress/expected/indexing.out
@@ -0,0 +1,330 @@
+-- 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, indparentidx::regclass
+    from pg_class left join pg_index on (indexrelid = oid)
+       where relname like 'idxpart%' order by relname;
+     relname     | relkind |  indparentidx  
+-----------------+---------+----------------
+ 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 cases
+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, make sure we don't create a separate
+-- 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 indexrelid::regclass, indrelid::regclass, indparentidx::regclass
+  from pg_index where indexrelid::regclass::text like 'idxpart%'
+  order by indexrelid::regclass;
+    indexrelid    | indrelid |  indparentidx   
+------------------+----------+-----------------
+ idxpart1_a_b_idx | idxpart1 | idxpart_a_b_idx
+ idxpart_a_b_idx  | idxpart  | -
+(2 rows)
+
+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 .. ATTACH/DETACH, error cases
+alter index idxpart attach partition idxpart1;
+ERROR:  "idxpart" is not an index
+alter index idxpart detach 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 detach partition idxpart1;
+ERROR:  "idxpart1" is not an index
+alter index idxpart_a_b_idx attach partition idxpart_a_b_idx;
+ERROR:  index "idxpart_a_b_idx" is not on a 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:  definition of index "idxpart1_tst1" is not compatible with index 
"idxpart_a_b_idx"
+alter index idxpart_a_b_idx attach partition idxpart1_tst2;
+ERROR:  definition of index "idxpart1_tst2" is not compatible with index 
"idxpart_a_b_idx"
+alter index idxpart_a_b_idx attach partition idxpart1_tst3;
+ERROR:  definition of index "idxpart1_tst3" is not compatible with index 
"idxpart_a_b_idx"
+-- 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
+alter index idxpart_a_b_idx detach partition idxpart1_a_b_idx;
+alter index idxpart_a_b_idx detach partition idxpart1_a_b_idx; -- quiet
+drop table idxpart;
+-- make sure everything's gone
+select indexrelid::regclass, indrelid::regclass, indparentidx::regclass
+  from pg_index where indexrelid::regclass::text like 'idxpart%';
+ indexrelid | indrelid | indparentidx 
+------------+----------+--------------
+(0 rows)
+
+-- 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 detached.
+\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)
+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, indparentidx::regclass
+  from pg_index where indexrelid::regclass::text like 'idxpart%'
+  order by indrelid::regclass;
+   indexrelid    | indrelid  | indparentidx  
+-----------------+-----------+---------------
+ 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, indparentidx::regclass
+  from pg_index where indexrelid::regclass::text like 'idxpart%'
+  order by indrelid::regclass;
+   indexrelid    | indrelid  |  indparentidx  
+-----------------+-----------+----------------
+ 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)
+
+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, indparentidx::regclass
+    from pg_class left join pg_index on (indexrelid = oid)
+       where relname like 'idxpart%' order by relname;
+     relname      | relkind | indparentidx 
+------------------+---------+--------------
+ 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, indparentidx::regclass
+    from pg_class left join pg_index on (indexrelid = oid)
+       where relname like 'idxpart%' order by relname;
+     relname      | relkind | indparentidx 
+------------------+---------+--------------
+ idxpart          | p       | 
+ idxpart1         | r       | 
+ idxpart1_a_idx   | i       | idxparti
+ idxpart1_b_c_idx | i       | idxparti2
+ idxparti         | I       | -
+ idxparti2        | I       | -
+(6 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);
+\d idxpart1
+              Table "public.idxpart1"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ c      | text    |           |          | 
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+Partition of: idxpart FOR VALUES FROM (0) TO (10)
+Indexes:
+    "idxpart1_a_idx" btree (a)
+    "idxpart1_c_b_idx" btree (c, b)
+
+drop table idxpart;
+-- Make sure things work if either table has dropped columns
+create table idxpart (a int, b int, c int, d int) partition by range (a);
+alter table idxpart drop column c;
+create index idxparti on idxpart (a);
+create index idxparti2 on idxpart (b, d);
+create table idxpart1 (like idxpart);
+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 |           |          | 
+ d      | integer |           |          | 
+Partition of: idxpart FOR VALUES FROM (0) TO (10)
+Indexes:
+    "idxpart1_a_idx" btree (a)
+    "idxpart1_b_d_idx" btree (b, d)
+
+select attrelid::regclass, attname, attnum from pg_attribute
+  where attrelid in ('idxpart'::regclass, 'idxpart1'::regclass) and attnum > 0
+  order by attrelid::regclass, attnum;
+ attrelid |           attname            | attnum 
+----------+------------------------------+--------
+ idxpart  | a                            |      1
+ idxpart  | b                            |      2
+ idxpart  | ........pg.dropped.3........ |      3
+ idxpart  | d                            |      4
+ idxpart1 | a                            |      1
+ idxpart1 | b                            |      2
+ idxpart1 | d                            |      3
+(7 rows)
+
+drop table idxpart;
+create table idxpart (a int, b int, c int) partition by range (a);
+create table idxpart1 (zz int, like idxpart, aa int);
+alter table idxpart1 drop column zz, drop column aa;
+create index idxparti on idxpart (a);
+create index idxparti2 on idxpart (b, c);
+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      | integer |           |          | 
+Partition of: idxpart FOR VALUES FROM (0) TO (10)
+Indexes:
+    "idxpart1_a_idx" btree (a)
+    "idxpart1_b_c_idx" btree (b, c)
+
+select attrelid::regclass, attname, attnum from pg_attribute
+  where attrelid in ('idxpart'::regclass, 'idxpart1'::regclass) and attnum > 0
+  order by attrelid::regclass, attnum;
+ attrelid |           attname            | attnum 
+----------+------------------------------+--------
+ idxpart  | a                            |      1
+ idxpart  | b                            |      2
+ idxpart  | c                            |      3
+ idxpart1 | ........pg.dropped.1........ |      1
+ idxpart1 | a                            |      2
+ idxpart1 | b                            |      3
+ idxpart1 | c                            |      4
+ idxpart1 | ........pg.dropped.5........ |      5
+(8 rows)
+
+drop table idxpart;
diff --git a/src/test/regress/parallel_schedule 
b/src/test/regress/parallel_schedule
index aa5e6af621..591e7da337 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -116,7 +116,7 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs 
prepare without_oid c
 # ----------
 # Another group of parallel tests
 # ----------
-test: identity partition_join reloptions
+test: identity partition_join reloptions indexing
 
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 3866314a92..d5b5ec8472 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -181,5 +181,6 @@ test: xml
 test: identity
 test: partition_join
 test: reloptions
+test: indexing
 test: event_trigger
 test: stats
diff --git a/src/test/regress/sql/indexing.sql 
b/src/test/regress/sql/indexing.sql
new file mode 100644
index 0000000000..53a28f120f
--- /dev/null
+++ b/src/test/regress/sql/indexing.sql
@@ -0,0 +1,142 @@
+-- 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, indparentidx::regclass
+    from pg_class left join pg_index on (indexrelid = oid)
+       where relname like 'idxpart%' order by relname;
+drop table idxpart;
+
+-- Some unsupported cases
+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, make sure we don't create a separate
+-- 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 indexrelid::regclass, indrelid::regclass, indparentidx::regclass
+  from pg_index where indexrelid::regclass::text like 'idxpart%'
+  order by indexrelid::regclass;
+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 .. ATTACH/DETACH, error cases
+alter index idxpart attach partition idxpart1;
+alter index idxpart detach partition idxpart1;
+alter index idxpart_a_b_idx attach partition idxpart1;
+alter index idxpart_a_b_idx detach 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
+alter index idxpart_a_b_idx detach partition idxpart1_a_b_idx;
+alter index idxpart_a_b_idx detach partition idxpart1_a_b_idx; -- quiet
+drop table idxpart;
+-- make sure everything's gone
+select indexrelid::regclass, indrelid::regclass, indparentidx::regclass
+  from pg_index where indexrelid::regclass::text like '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 detached.
+\d idxpart1
+\d idxpart2
+\d idxpart21
+select indexrelid::regclass, indrelid::regclass, indparentidx::regclass
+  from pg_index where indexrelid::regclass::text like 'idxpart%'
+  order by indrelid::regclass;
+alter index idxpart2_a_idx attach partition idxpart22_a_idx;
+select indexrelid::regclass, indrelid::regclass, indparentidx::regclass
+  from pg_index where indexrelid::regclass::text like 'idxpart%'
+  order by indrelid::regclass;
+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, indparentidx::regclass
+    from pg_class left join pg_index on (indexrelid = oid)
+       where relname like 'idxpart%' order by relname;
+alter table idxpart attach partition idxpart1 for values from (0) to (10);
+\d idxpart1
+select relname, relkind, indparentidx::regclass
+    from pg_class left join pg_index on (indexrelid = oid)
+       where 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);
+\d idxpart1
+drop table idxpart;
+
+-- Make sure things work if either table has dropped columns
+create table idxpart (a int, b int, c int, d int) partition by range (a);
+alter table idxpart drop column c;
+create index idxparti on idxpart (a);
+create index idxparti2 on idxpart (b, d);
+create table idxpart1 (like idxpart);
+alter table idxpart attach partition idxpart1 for values from (0) to (10);
+\d idxpart1
+select attrelid::regclass, attname, attnum from pg_attribute
+  where attrelid in ('idxpart'::regclass, 'idxpart1'::regclass) and attnum > 0
+  order by attrelid::regclass, attnum;
+drop table idxpart;
+
+create table idxpart (a int, b int, c int) partition by range (a);
+create table idxpart1 (zz int, like idxpart, aa int);
+alter table idxpart1 drop column zz, drop column aa;
+create index idxparti on idxpart (a);
+create index idxparti2 on idxpart (b, c);
+alter table idxpart attach partition idxpart1 for values from (0) to (10);
+\d idxpart1
+select attrelid::regclass, attname, attnum from pg_attribute
+  where attrelid in ('idxpart'::regclass, 'idxpart1'::regclass) and attnum > 0
+  order by attrelid::regclass, attnum;
+drop table idxpart;
-- 
2.11.0

>From 296715b1526515fd7e9b6bbf651255e9a02e3ea1 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvhe...@alvh.no-ip.org>
Date: Mon, 6 Nov 2017 17:04:55 +0100
Subject: [PATCH v5 4/4] allow indexes on partitioned tables to be unique

---
 src/backend/commands/indexcmds.c           |  74 ++++++++++++++--
 src/backend/parser/parse_utilcmd.c         |  24 -----
 src/test/regress/expected/alter_table.out  |   8 --
 src/test/regress/expected/create_table.out |  12 ---
 src/test/regress/expected/indexing.out     | 135 ++++++++++++++++++++++++++++-
 src/test/regress/sql/alter_table.sql       |   2 -
 src/test/regress/sql/create_table.sql      |   8 --
 src/test/regress/sql/indexing.sql          |  72 ++++++++++++++-
 8 files changed, 269 insertions(+), 66 deletions(-)

diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index df8ec66929..b31c781bd8 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -425,20 +425,11 @@ DefineIndex(Oid relationId,
                                        (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 (is_alter_table)
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                        errmsg("cannot create constraints on 
partitioned tables")));
        }
 
        /*
@@ -635,6 +626,71 @@ DefineIndex(Oid relationId,
                index_check_primary_key(rel, indexInfo, is_alter_table);
 
        /*
+        * If this table is partitioned and we're creating a unique index or a
+        * primary key, make sure that the indexed columns are part of the
+        * partition key.  Otherwise it would be possible to violate uniqueness 
by
+        * putting values that ought to be unique in different partitions.
+        *
+        * We could lift this limitation if we had global indexes, but those 
have
+        * their own problems, so this is a useful feature combination.
+        *
+        * XXX in some cases rel->rd_partkey is NULL, which caused this code to
+        * crash.  In what cases can that happen?
+        */
+       if (partitioned && (stmt->unique || stmt->primary) &&
+               (rel->rd_partkey != NULL))
+       {
+               int                     i;
+               PartitionKey key = rel->rd_partkey;
+
+               /*
+                * A partitioned table can have unique indexes, as long as all 
the
+                * columns in the unique key appear in the partition key.  
Because
+                * partitions are non-overlapping, this guarantees that there 
is a
+                * single partition that can contain any given key, and thus the
+                * uniqueness is guaranteed globally.
+                */
+
+               for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
+               {
+                       bool    found;
+                       int             j;
+
+                       /*
+                        * Expression indexes are not supported yet.  We can 
probably use
+                        * pull_varattnos() on the expression, and verify that 
all the
+                        * vars mentioned correspond to columns of the 
partition key.
+                        */
+                       if (indexInfo->ii_KeyAttrNumbers[i] == 0)
+                               ereport(ERROR,
+                                          
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                               errmsg("UNIQUE indexes 
containing expressions are not supported")));
+
+                       found = false;
+                       for (j = 0; j < key->partnatts; j++)
+                       {
+                               if (indexInfo->ii_KeyAttrNumbers[i] == 
key->partattrs[j])
+                               {
+                                       found = true;
+                                       break;  /* all good */
+                               }
+                       }
+                       if (!found)
+                       {
+                               char       *attname;
+
+                               attname = 
NameStr(TupleDescAttr(RelationGetDescr(rel),
+                                                                               
                indexInfo->ii_KeyAttrNumbers[i] - 1)->attname);
+                               ereport(ERROR,
+                                               
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                                errmsg("UNIQUE index on table 
\"%s\" contains column \"%s\" which is not part of the partition key",
+                                                               
RelationGetRelationName(rel), attname)));
+                       }
+               }
+       }
+
+
+       /*
         * We disallow indexes on system columns other than OID.  They would not
         * necessarily get updated correctly, and they don't seem useful anyway.
         */
diff --git a/src/backend/parser/parse_utilcmd.c 
b/src/backend/parser/parse_utilcmd.c
index 938e73f8ad..364bd4c81d 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -692,12 +692,6 @@ transformColumnDefinition(CreateStmtContext *cxt, 
ColumnDef *column)
                                                         errmsg("primary key 
constraints are not supported on foreign tables"),
                                                         
parser_errposition(cxt->pstate,
                                                                                
                constraint->location)));
-                               if (cxt->ispartitioned)
-                                       ereport(ERROR,
-                                                       
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                        errmsg("primary key 
constraints are not supported on partitioned tables"),
-                                                        
parser_errposition(cxt->pstate,
-                                                                               
                constraint->location)));
                                /* FALL THRU */
 
                        case CONSTR_UNIQUE:
@@ -707,12 +701,6 @@ transformColumnDefinition(CreateStmtContext *cxt, 
ColumnDef *column)
                                                         errmsg("unique 
constraints are not supported on foreign tables"),
                                                         
parser_errposition(cxt->pstate,
                                                                                
                constraint->location)));
-                               if (cxt->ispartitioned)
-                                       ereport(ERROR,
-                                                       
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                        errmsg("unique 
constraints are not supported on partitioned tables"),
-                                                        
parser_errposition(cxt->pstate,
-                                                                               
                constraint->location)));
                                if (constraint->keys == NIL)
                                        constraint->keys = 
list_make1(makeString(column->colname));
                                cxt->ixconstraints = 
lappend(cxt->ixconstraints, constraint);
@@ -809,12 +797,6 @@ transformTableConstraint(CreateStmtContext *cxt, 
Constraint *constraint)
                                                 errmsg("primary key 
constraints are not supported on foreign tables"),
                                                 parser_errposition(cxt->pstate,
                                                                                
        constraint->location)));
-                       if (cxt->ispartitioned)
-                               ereport(ERROR,
-                                               
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                errmsg("primary key 
constraints are not supported on partitioned tables"),
-                                                parser_errposition(cxt->pstate,
-                                                                               
        constraint->location)));
                        cxt->ixconstraints = lappend(cxt->ixconstraints, 
constraint);
                        break;
 
@@ -825,12 +807,6 @@ transformTableConstraint(CreateStmtContext *cxt, 
Constraint *constraint)
                                                 errmsg("unique constraints are 
not supported on foreign tables"),
                                                 parser_errposition(cxt->pstate,
                                                                                
        constraint->location)));
-                       if (cxt->ispartitioned)
-                               ereport(ERROR,
-                                               
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                                errmsg("unique constraints are 
not supported on partitioned tables"),
-                                                parser_errposition(cxt->pstate,
-                                                                               
        constraint->location)));
                        cxt->ixconstraints = lappend(cxt->ixconstraints, 
constraint);
                        break;
 
diff --git a/src/test/regress/expected/alter_table.out 
b/src/test/regress/expected/alter_table.out
index 5a96b0b5e1..e7cf6ae470 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -3229,14 +3229,6 @@ CREATE TABLE partitioned (
        a int,
        b int
 ) PARTITION BY RANGE (a, (a+b+1));
-ALTER TABLE partitioned ADD UNIQUE (a);
-ERROR:  unique constraints are not supported on partitioned tables
-LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
-                                    ^
-ALTER TABLE partitioned ADD PRIMARY KEY (a);
-ERROR:  primary key constraints are not supported on partitioned tables
-LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
-                                    ^
 ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
 ERROR:  foreign key constraints are not supported on partitioned tables
 LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
diff --git a/src/test/regress/expected/create_table.out 
b/src/test/regress/expected/create_table.out
index 335cd37e18..a89e0c17df 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -276,12 +276,6 @@ CREATE TABLE partitioned (
 ) PARTITION BY LIST (a1, a2);  -- fail
 ERROR:  cannot use "list" partition strategy with more than one column
 -- unsupported constraint type for partitioned tables
-CREATE TABLE partitioned (
-       a int PRIMARY KEY
-) PARTITION BY RANGE (a);
-ERROR:  primary key constraints are not supported on partitioned tables
-LINE 2:  a int PRIMARY KEY
-               ^
 CREATE TABLE pkrel (
        a int PRIMARY KEY
 );
@@ -293,12 +287,6 @@ LINE 2:  a int REFERENCES pkrel(a)
                ^
 DROP TABLE pkrel;
 CREATE TABLE partitioned (
-       a int UNIQUE
-) PARTITION BY RANGE (a);
-ERROR:  unique constraints are not supported on partitioned tables
-LINE 2:  a int UNIQUE
-               ^
-CREATE TABLE partitioned (
        a int,
        EXCLUDE USING gist (a WITH &&)
 ) PARTITION BY RANGE (a);
diff --git a/src/test/regress/expected/indexing.out 
b/src/test/regress/expected/indexing.out
index cde29bebb5..7ec8c5e3f7 100644
--- a/src/test/regress/expected/indexing.out
+++ b/src/test/regress/expected/indexing.out
@@ -25,8 +25,6 @@ drop table idxpart;
 -- Some unsupported cases
 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;
@@ -328,3 +326,136 @@ select attrelid::regclass, attname, attnum from 
pg_attribute
 (8 rows)
 
 drop table idxpart;
+--
+-- Constraint-related indexes
+--
+-- Verify that it works to add primary key / unique to partitioned tables
+create table idxpart (a int primary key, b int) partition by range (b);
+ERROR:  UNIQUE index on table "idxpart" contains column "a" which is not part 
of the partition key
+create table idxpart (a int primary key, b int) partition by range (a);
+\d idxpart
+              Table "public.idxpart"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           | not null | 
+ b      | integer |           |          | 
+Partition key: RANGE (a)
+Indexes:
+    "idxpart_pkey" PRIMARY KEY, btree (a)
+
+drop table idxpart;
+create table idxpart (a int unique, b int) partition by range (a);
+\d idxpart
+              Table "public.idxpart"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+Partition key: RANGE (a)
+Indexes:
+    "idxpart_a_key" UNIQUE CONSTRAINT, btree (a)
+
+drop table idxpart;
+-- but not other types of index-based constraints
+create table idxpart (a int, exclude (a with = )) partition by range (a);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 1: create table idxpart (a int, exclude (a with = )) partition ...
+                                     ^
+-- It works to add primary keys after the partitioned table is created
+create table idxpart (a int, b int) partition by range (a);
+alter table idxpart add primary key (a);
+\d idxpart
+              Table "public.idxpart"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           | not null | 
+ b      | integer |           |          | 
+Partition key: RANGE (a)
+Indexes:
+    "idxpart_pkey" PRIMARY KEY, btree (a)
+
+drop table idxpart;
+-- It works to add unique constraints after the partitioned table is created
+create table idxpart (a int, b int) partition by range (a);
+alter table idxpart add unique (a);
+\d idxpart
+              Table "public.idxpart"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+ b      | integer |           |          | 
+Partition key: RANGE (a)
+Indexes:
+    "idxpart_a_key" UNIQUE CONSTRAINT, btree (a)
+
+drop table idxpart;
+-- Exclusion constraints cannot be added
+create table idxpart (a int, b int) partition by range (a);
+alter table idxpart add exclude (a with =);
+ERROR:  exclusion constraints are not supported on partitioned tables
+LINE 1: alter table idxpart add exclude (a with =);
+                                ^
+drop table idxpart;
+-- It is an error to add a constraint to columns that are not in the partition
+-- key.
+create table idxpart (a int, b int, primary key (a, b)) partition by range (a);
+ERROR:  UNIQUE index on table "idxpart" contains column "b" which is not part 
of the partition key
+create table idxpart (a int, b int, primary key (a, b)) partition by range (b);
+ERROR:  UNIQUE index on table "idxpart" contains column "a" which is not part 
of the partition key
+create table idxpart (a int, b int, unique (a, b)) partition by range (a);
+ERROR:  UNIQUE index on table "idxpart" contains column "b" which is not part 
of the partition key
+create table idxpart (a int, b int, unique (a, b)) partition by range (b);
+ERROR:  UNIQUE index on table "idxpart" contains column "a" which is not part 
of the partition key
+-- but using a partial set of columns is okay
+create table idxpart (a int, b int, c int primary key) partition by range (b);
+ERROR:  UNIQUE index on table "idxpart" contains column "c" which is not part 
of the partition key
+drop table idxpart;
+ERROR:  table "idxpart" does not exist
+create table idxpart (a int, b int, primary key (a)) partition by range (a, b);
+drop table idxpart;
+create table idxpart (a int, b int, primary key (b)) partition by range (a, b);
+drop table idxpart;
+create table idxpart (a int, b int, c int unique) partition by range (b);
+ERROR:  UNIQUE index on table "idxpart" contains column "c" which is not part 
of the partition key
+drop table idxpart;
+ERROR:  table "idxpart" does not exist
+create table idxpart (a int, b int, unique (a)) partition by range (a, b);
+drop table idxpart;
+create table idxpart (a int, b int, unique (b)) partition by range (a, b);
+drop table idxpart;
+-- When (sub)partitions are created, they also contain the constraint
+create table idxpart (a int, b int, primary key (a, b)) partition by range (a, 
b);
+create table idxpart1 partition of idxpart for values from (1, 1) to (10, 10);
+create table idxpart2 partition of idxpart for values from (10, 10) to (20, 20)
+  partition by range (b);
+create table idxpart21 partition of idxpart2 for values from (10) to (15);
+create table idxpart22 partition of idxpart2 for values from (15) to (20);
+create table idxpart3 (b int not null, a int not null);
+alter table idxpart attach partition idxpart3 for values from (20, 20) to (30, 
30);
+select conname, contype, conrelid::regclass, conindid::regclass, conkey
+  from pg_constraint where conrelid::regclass::text like 'idxpart%'
+  order by conname;
+    conname     | contype | conrelid  |    conindid    | conkey 
+----------------+---------+-----------+----------------+--------
+ idxpart1_pkey  | p       | idxpart1  | idxpart1_pkey  | {1,2}
+ idxpart21_pkey | p       | idxpart21 | idxpart21_pkey | {1,2}
+ idxpart22_pkey | p       | idxpart22 | idxpart22_pkey | {1,2}
+ idxpart2_pkey  | p       | idxpart2  | idxpart2_pkey  | {1,2}
+ idxpart3_pkey  | p       | idxpart3  | idxpart3_pkey  | {2,1}
+ idxpart_pkey   | p       | idxpart   | idxpart_pkey   | {1,2}
+(6 rows)
+
+drop table idxpart;
+create table idxpart (a int primary key, b int) partition by range (a);
+create table idxpart1 partition of idxpart for values from (1) to (10);
+\d idxpart1
+              Table "public.idxpart1"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           | not null | 
+ b      | integer |           |          | 
+Partition of: idxpart FOR VALUES FROM (1) TO (10)
+Indexes:
+    "idxpart1_pkey" PRIMARY KEY, btree (a)
+
+drop table idxpart;
diff --git a/src/test/regress/sql/alter_table.sql 
b/src/test/regress/sql/alter_table.sql
index 02a33ca7c4..115ed1efad 100644
--- a/src/test/regress/sql/alter_table.sql
+++ b/src/test/regress/sql/alter_table.sql
@@ -2000,8 +2000,6 @@ CREATE TABLE partitioned (
        a int,
        b int
 ) PARTITION BY RANGE (a, (a+b+1));
-ALTER TABLE partitioned ADD UNIQUE (a);
-ALTER TABLE partitioned ADD PRIMARY KEY (a);
 ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
 ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
 
diff --git a/src/test/regress/sql/create_table.sql 
b/src/test/regress/sql/create_table.sql
index b77b476436..673db6b77d 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -294,10 +294,6 @@ CREATE TABLE partitioned (
 ) PARTITION BY LIST (a1, a2);  -- fail
 
 -- unsupported constraint type for partitioned tables
-CREATE TABLE partitioned (
-       a int PRIMARY KEY
-) PARTITION BY RANGE (a);
-
 CREATE TABLE pkrel (
        a int PRIMARY KEY
 );
@@ -307,10 +303,6 @@ CREATE TABLE partitioned (
 DROP TABLE pkrel;
 
 CREATE TABLE partitioned (
-       a int UNIQUE
-) PARTITION BY RANGE (a);
-
-CREATE TABLE partitioned (
        a int,
        EXCLUDE USING gist (a WITH &&)
 ) PARTITION BY RANGE (a);
diff --git a/src/test/regress/sql/indexing.sql 
b/src/test/regress/sql/indexing.sql
index 53a28f120f..0831837560 100644
--- a/src/test/regress/sql/indexing.sql
+++ b/src/test/regress/sql/indexing.sql
@@ -14,7 +14,6 @@ drop table idxpart;
 -- Some unsupported cases
 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;
 
@@ -140,3 +139,74 @@ select attrelid::regclass, attname, attnum from 
pg_attribute
   where attrelid in ('idxpart'::regclass, 'idxpart1'::regclass) and attnum > 0
   order by attrelid::regclass, attnum;
 drop table idxpart;
+
+--
+-- Constraint-related indexes
+--
+
+-- Verify that it works to add primary key / unique to partitioned tables
+create table idxpart (a int primary key, b int) partition by range (b);
+create table idxpart (a int primary key, b int) partition by range (a);
+\d idxpart
+drop table idxpart;
+create table idxpart (a int unique, b int) partition by range (a);
+\d idxpart
+drop table idxpart;
+-- but not other types of index-based constraints
+create table idxpart (a int, exclude (a with = )) partition by range (a);
+
+-- It works to add primary keys after the partitioned table is created
+create table idxpart (a int, b int) partition by range (a);
+alter table idxpart add primary key (a);
+\d idxpart
+drop table idxpart;
+
+-- It works to add unique constraints after the partitioned table is created
+create table idxpart (a int, b int) partition by range (a);
+alter table idxpart add unique (a);
+\d idxpart
+drop table idxpart;
+
+-- Exclusion constraints cannot be added
+create table idxpart (a int, b int) partition by range (a);
+alter table idxpart add exclude (a with =);
+drop table idxpart;
+
+-- It is an error to add a constraint to columns that are not in the partition
+-- key.
+create table idxpart (a int, b int, primary key (a, b)) partition by range (a);
+create table idxpart (a int, b int, primary key (a, b)) partition by range (b);
+create table idxpart (a int, b int, unique (a, b)) partition by range (a);
+create table idxpart (a int, b int, unique (a, b)) partition by range (b);
+-- but using a partial set of columns is okay
+create table idxpart (a int, b int, c int primary key) partition by range (b);
+drop table idxpart;
+create table idxpart (a int, b int, primary key (a)) partition by range (a, b);
+drop table idxpart;
+create table idxpart (a int, b int, primary key (b)) partition by range (a, b);
+drop table idxpart;
+create table idxpart (a int, b int, c int unique) partition by range (b);
+drop table idxpart;
+create table idxpart (a int, b int, unique (a)) partition by range (a, b);
+drop table idxpart;
+create table idxpart (a int, b int, unique (b)) partition by range (a, b);
+drop table idxpart;
+
+-- When (sub)partitions are created, they also contain the constraint
+create table idxpart (a int, b int, primary key (a, b)) partition by range (a, 
b);
+create table idxpart1 partition of idxpart for values from (1, 1) to (10, 10);
+create table idxpart2 partition of idxpart for values from (10, 10) to (20, 20)
+  partition by range (b);
+create table idxpart21 partition of idxpart2 for values from (10) to (15);
+create table idxpart22 partition of idxpart2 for values from (15) to (20);
+create table idxpart3 (b int not null, a int not null);
+alter table idxpart attach partition idxpart3 for values from (20, 20) to (30, 
30);
+select conname, contype, conrelid::regclass, conindid::regclass, conkey
+  from pg_constraint where conrelid::regclass::text like 'idxpart%'
+  order by conname;
+drop table idxpart;
+
+create table idxpart (a int primary key, b int) partition by range (a);
+create table idxpart1 partition of idxpart for values from (1) to (10);
+\d idxpart1
+drop table idxpart;
-- 
2.11.0

Reply via email to