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