<para>
Create an index on the key column(s), as well as any other indexes you
might want, on the partitioned table. (The key index is not strictly
- necessary, but in most scenarios it is helpful. If you intend the key
- values to be unique then you should always create a unique or
- primary-key constraint for each partition.) This automatically creates
+ necessary, but in most scenarios it is helpful.)
+ This automatically creates
one index on each partition, and any partitions you create or attach
later will also contain the index.
<itemizedlist>
<listitem>
<para>
- There is no way to create a primary key, unique constraint, or
+ There is no way to create a
exclusion constraint spanning all partitions; it is only possible
to constrain each leaf partition individually.
</para>
<listitem>
<para>
- Since primary keys are not supported on partitioned tables, foreign
+ While primary keys are supported on partitioned tables, foreign
keys referencing partitioned tables are not supported, nor are foreign
key references from a partitioned table to some other table.
</para>
disappear too.
</para>
+ <para>
+ Additional restrictions apply when unique or primary key constraints
+ are added to partitioned tables; see <xref linkend="sql-createtable" />.
+ </para>
+
<note>
<para>
Adding a constraint using an existing index can be helpful in
<para>
This form attaches an existing table (which might itself be partitioned)
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>. For each index in the target table, a corresponding
+ as a partition for specific values using <literal>FOR VALUES</literal>
+ or as a default partition by using <literal>DEFAULT</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.
as the target table and no more; moreover, the column types must also
match. Also, it must have all the <literal>NOT NULL</literal> and
<literal>CHECK</literal> constraints of the target table. Currently
- <literal>UNIQUE</literal>, <literal>PRIMARY KEY</literal>, and
<literal>FOREIGN KEY</literal> constraints are not considered.
+ <literal>UNIQUE</literal> and <literal>PRIMARY KEY</literal> constraints
+ from the parent table will be created in the partition, if they don't
+ already exist.
If any of the <literal>CHECK</literal> constraints of the table being
attached is marked <literal>NO INHERIT</literal>, the command will fail;
such a constraint must be recreated without the <literal>NO INHERIT</literal>
insert or update data which would result in duplicate entries
will generate an error.
</para>
+
+ <para>
+ Additional restrictions apply when unique indexes are applied to
+ partitioned tables; see <xref linkend="sql-createtable" />.
+ </para>
</listitem>
</varlistentry>
</para>
<para>
- Partitioned tables do not support <literal>UNIQUE</literal>,
- <literal>PRIMARY KEY</literal>, <literal>EXCLUDE</literal>, or
+ Partitioned tables do not support <literal>EXCLUDE</literal> or
<literal>FOREIGN KEY</literal> constraints; however, you can define
these constraints on individual partitions.
</para>
primary key constraint defined for the table. (Otherwise it
would just be the same constraint listed twice.)
</para>
+
+ <para>
+ When used on partitioned tables, unique constraints must include all the
+ columns of the partition key.
+ If any partitions are in turn partitioned, all columns of each partition
+ key are considered at each level below the <literal>UNIQUE</literal>
+ constraint.
+ </para>
</listitem>
</varlistentry>
about the design of the schema, since a primary key implies that other
tables can rely on this set of columns as a unique identifier for rows.
</para>
+
+ <para>
+ <literal>PRIMARY KEY</literal> constraints share the restrictions that
+ <literal>UNIQUE</literal> constraints have when placed on partitioned
+ tables.
+ </para>
+
</listitem>
</varlistentry>
stmt,
$4,
InvalidOid,
+ InvalidOid,
false,
false,
false,
stmt,
$5,
InvalidOid,
+ InvalidOid,
false,
false,
false,
* nonzero to specify a preselected OID.
* parentIndexRelid: if creating an index partition, the OID of the
* parent index; otherwise InvalidOid.
+ * parentConstraintId: if creating a constraint on a partition, the OID
+ * of the constraint in the parent; 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
* (only if INDEX_CREATE_ADD_CONSTRAINT is set)
* allow_system_table_mods: allow table to be a system catalog
* is_internal: if true, post creation hook for new index
+ * constraintId: if not NULL, receives OID of created constraint
*
* Returns the OID of the created index.
*/
const char *indexRelationName,
Oid indexRelationId,
Oid parentIndexRelid,
+ Oid parentConstraintId,
Oid relFileNode,
IndexInfo *indexInfo,
List *indexColNames,
bits16 flags,
bits16 constr_flags,
bool allow_system_table_mods,
- bool is_internal)
+ bool is_internal,
+ Oid *constraintId)
{
Oid heapRelationId = RelationGetRelid(heapRelation);
Relation pg_class;
if ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0)
{
char constraintType;
+ ObjectAddress localaddr;
if (isprimary)
constraintType = CONSTRAINT_PRIMARY;
constraintType = 0; /* keep compiler quiet */
}
- index_constraint_create(heapRelation,
+ localaddr = index_constraint_create(heapRelation,
indexRelationId,
+ parentConstraintId,
indexInfo,
indexRelationName,
constraintType,
constr_flags,
allow_system_table_mods,
is_internal);
+ if (constraintId)
+ *constraintId = localaddr.objectId;
}
else
{
*
* heapRelation: table owning the index (must be suitably locked by caller)
* indexRelationId: OID of the index
+ * parentConstraintId: if constraint is on a partition, the OID of the
+ * constraint in the parent.
* indexInfo: same info executor uses to insert into the index
* constraintName: what it say (generally, should match name of index)
* constraintType: one of CONSTRAINT_PRIMARY, CONSTRAINT_UNIQUE, or
ObjectAddress
index_constraint_create(Relation heapRelation,
Oid indexRelationId,
+ Oid parentConstraintId,
IndexInfo *indexInfo,
const char *constraintName,
char constraintType,
bool deferrable;
bool initdeferred;
bool mark_as_primary;
+ bool islocal;
+ bool noinherit;
+ int inhcount;
deferrable = (constr_flags & INDEX_CONSTR_CREATE_DEFERRABLE) != 0;
initdeferred = (constr_flags & INDEX_CONSTR_CREATE_INIT_DEFERRED) != 0;
deleteDependencyRecordsForClass(RelationRelationId, indexRelationId,
RelationRelationId, DEPENDENCY_AUTO);
+ if (OidIsValid(parentConstraintId))
+ {
+ islocal = false;
+ inhcount = 1;
+ noinherit = false;
+ }
+ else
+ {
+ islocal = true;
+ inhcount = 0;
+ noinherit = true;
+ }
+
/*
* Construct a pg_constraint entry.
*/
NULL, /* no check constraint */
NULL,
NULL,
- true, /* islocal */
- 0, /* inhcount */
- true, /* noinherit */
+ islocal,
+ inhcount,
+ noinherit,
is_internal);
/*
recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
+ /*
+ * Also, if this is a constraint on a partition, mark it as depending
+ * on the constraint in the parent.
+ */
+ if (OidIsValid(parentConstraintId))
+ {
+ ObjectAddress parentConstr;
+
+ ObjectAddressSet(parentConstr, ConstraintRelationId, parentConstraintId);
+ recordDependencyOn(&referenced, &parentConstr, DEPENDENCY_INTERNAL_AUTO);
+ }
+
/*
* If the constraint is deferrable, create the deferred uniqueness
* checking trigger. (The trigger will be given an internal dependency on
heap_close(conRel, RowExclusiveLock);
}
+/*
+ * ConstraintSetParentConstraint
+ * Set a partition's constraint as child of its parent table's
+ *
+ * This updates the constraint's pg_constraint row to show it as inherited, and
+ * add a dependency to the parent so that it cannot be removed on its own.
+ */
+void
+ConstraintSetParentConstraint(Oid childConstrId, Oid parentConstrId)
+{
+ Relation constrRel;
+ Form_pg_constraint constrForm;
+ HeapTuple tuple,
+ newtup;
+ ObjectAddress depender;
+ ObjectAddress referenced;
+
+ constrRel = heap_open(ConstraintRelationId, RowExclusiveLock);
+ tuple = SearchSysCache1(CONSTROID, ObjectIdGetDatum(childConstrId));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for constraint %u", childConstrId);
+ newtup = heap_copytuple(tuple);
+ constrForm = (Form_pg_constraint) GETSTRUCT(newtup);
+ constrForm->conislocal = false;
+ constrForm->coninhcount++;
+ CatalogTupleUpdate(constrRel, &tuple->t_self, newtup);
+ ReleaseSysCache(tuple);
+
+ ObjectAddressSet(referenced, ConstraintRelationId, parentConstrId);
+ ObjectAddressSet(depender, ConstraintRelationId, childConstrId);
+
+ recordDependencyOn(&depender, &referenced, DEPENDENCY_INTERNAL_AUTO);
+
+ heap_close(constrRel, RowExclusiveLock);
+}
+
+
/*
* get_relation_constraint_oid
* Find a constraint on the specified relation with the specified name.
return conattnos;
}
+/*
+ * Return the OID of the constraint associated with the given index in the
+ * given relation; or InvalidOid if no such index is catalogued.
+ */
+Oid
+get_relation_idx_constraint_oid(Oid relationId, Oid indexId)
+{
+ Relation pg_constraint;
+ SysScanDesc scan;
+ ScanKeyData key;
+ HeapTuple tuple;
+ Oid constraintId = InvalidOid;
+
+ pg_constraint = heap_open(ConstraintRelationId, AccessShareLock);
+
+ ScanKeyInit(&key,
+ Anum_pg_constraint_conrelid,
+ BTEqualStrategyNumber,
+ F_OIDEQ,
+ ObjectIdGetDatum(relationId));
+ scan = systable_beginscan(pg_constraint, ConstraintRelidIndexId,
+ true, NULL, 1, &key);
+ while ((tuple = systable_getnext(scan)) != NULL)
+ {
+ Form_pg_constraint constrForm;
+
+ constrForm = (Form_pg_constraint) GETSTRUCT(tuple);
+ if (constrForm->conindid == indexId)
+ {
+ constraintId = HeapTupleGetOid(tuple);
+ break;
+ }
+ }
+ systable_endscan(scan);
+
+ heap_close(pg_constraint, AccessShareLock);
+ return constraintId;
+}
+
/*
* get_domain_constraint_oid
* Find a constraint on the specified domain with the specified name.
coloptions[1] = 0;
index_create(toast_rel, toast_idxname, toastIndexOid, InvalidOid,
- InvalidOid,
+ InvalidOid, InvalidOid,
indexInfo,
list_make2("chunk_id", "chunk_seq"),
BTREE_AM_OID,
rel->rd_rel->reltablespace,
collationObjectId, classObjectId, coloptions, (Datum) 0,
- INDEX_CREATE_IS_PRIMARY, 0, true, true);
+ INDEX_CREATE_IS_PRIMARY, 0, true, true, NULL);
heap_close(toast_rel, NoLock);
#include "catalog/indexing.h"
#include "catalog/partition.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_constraint_fn.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_opclass.h"
* 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.
+ * 'parentConstraintId': the OID of the parent constraint; InvalidOid if not
+ * the child of a constraint (only used when recursing)
* '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.)
IndexStmt *stmt,
Oid indexRelationId,
Oid parentIndexId,
+ Oid parentConstraintId,
bool is_alter_table,
bool check_rights,
bool check_not_in_use,
Oid accessMethodId;
Oid namespaceId;
Oid tablespaceId;
+ Oid createdConstraintId = InvalidOid;
List *indexColNames;
Relation rel;
Relation indexRelation;
(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")));
}
/*
if (stmt->primary)
index_check_primary_key(rel, indexInfo, is_alter_table);
+ /*
+ * If this table is partitioned and we're creating a unique index or a
+ * primary key, make sure that the indexed columns are part of the
+ * partition key. Otherwise it would be possible to violate uniqueness by
+ * putting values that ought to be unique in different partitions.
+ *
+ * We could lift this limitation if we had global indexes, but those have
+ * their own problems, so this is a useful feature combination.
+ */
+ if (partitioned && (stmt->unique || stmt->primary))
+ {
+ PartitionKey key = rel->rd_partkey;
+ int i;
+
+ /*
+ * A partitioned table can have unique indexes, as long as all the
+ * columns in the partition key appear in the unique key. A
+ * partition-local index can enforce global uniqueness iff the PK
+ * value completely determines the partition that a row is in.
+ *
+ * Thus, verify that all the columns in the partition key appear
+ * in the unique key definition.
+ */
+ for (i = 0; i < key->partnatts; i++)
+ {
+ bool found = false;
+ int j;
+ const char *constraint_type;
+
+ if (stmt->primary)
+ constraint_type = "PRIMARY KEY";
+ else if (stmt->unique)
+ constraint_type = "UNIQUE";
+ else if (stmt->excludeOpNames != NIL)
+ constraint_type = "EXCLUDE";
+ else
+ {
+ elog(ERROR, "unknown constraint type");
+ constraint_type = NULL; /* keep compiler quiet */
+ }
+
+ /*
+ * It may be possible to support UNIQUE constraints when partition
+ * keys are expressions, but is it worth it? Give up for now.
+ */
+ if (key->partattrs[i] == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unsupported %s constraint with partition key definition",
+ constraint_type),
+ errdetail("%s constraints cannot be used when partition keys include expressions.",
+ constraint_type)));
+
+ for (j = 0; j < indexInfo->ii_NumIndexAttrs; j++)
+ {
+ if (key->partattrs[i] == indexInfo->ii_KeyAttrNumbers[j])
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ {
+ Form_pg_attribute att;
+
+ att = TupleDescAttr(RelationGetDescr(rel), key->partattrs[i] - 1);
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("insufficient columns in %s constraint definition",
+ constraint_type),
+ errdetail("%s constraint on table \"%s\" lacks column \"%s\" which is part of the partition key.",
+ constraint_type, RelationGetRelationName(rel),
+ NameStr(att->attname))));
+ }
+ }
+ }
+
+
/*
* We disallow indexes on system columns other than OID. They would not
* necessarily get updated correctly, and they don't seem useful anyway.
indexRelationId =
index_create(rel, indexRelationName, indexRelationId, parentIndexId,
+ parentConstraintId,
stmt->oldNode, indexInfo, indexColNames,
accessMethodId, tablespaceId,
collationObjectId, classObjectId,
coloptions, reloptions,
flags, constr_flags,
- allowSystemTableMods, !check_rights);
+ allowSystemTableMods, !check_rights,
+ &createdConstraintId);
ObjectAddressSet(address, RelationRelationId, indexRelationId);
opfamOids,
attmap, maplen))
{
+ Oid cldConstrOid = InvalidOid;
+
/*
- * Found a match. Attach index to parent and we're
- * done, but keep lock till commit.
+ * Found a match.
+ *
+ * If this index is being created in the parent
+ * because of a constraint, then the child needs to
+ * have a constraint also, so look for one. If there
+ * is no such constraint, this index is no good, so
+ * keep looking.
*/
+ if (createdConstraintId != InvalidOid)
+ {
+ cldConstrOid =
+ get_relation_idx_constraint_oid(childRelid,
+ cldidxid);
+ if (cldConstrOid == InvalidOid)
+ {
+ index_close(cldidx, lockmode);
+ continue;
+ }
+ }
+
+ /* Attach index to parent and we're done. */
IndexSetParentIndex(cldidx, indexRelationId);
+ if (createdConstraintId != InvalidOid)
+ ConstraintSetParentConstraint(cldConstrOid,
+ createdConstraintId);
if (!IndexIsValid(cldidx->rd_index))
invalidate_parent = true;
found = true;
+ /* keep lock till commit */
index_close(cldidx, NoLock);
break;
}
DefineIndex(childRelid, childStmt,
InvalidOid, /* no predefined OID */
indexRelationId, /* this is our child */
+ createdConstraintId,
false, check_rights, check_not_in_use,
false, quiet);
}
Relation idxRel = index_open(lfirst_oid(cell), AccessShareLock);
AttrNumber *attmap;
IndexStmt *idxstmt;
+ Oid constraintOid;
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);
+ attmap, RelationGetDescr(rel)->natts,
+ &constraintOid);
DefineIndex(RelationGetRelid(rel),
idxstmt,
InvalidOid,
RelationGetRelid(idxRel),
+ constraintOid,
false, false, false, false, false);
index_close(idxRel, AccessShareLock);
stmt,
InvalidOid, /* no predefined OID */
InvalidOid, /* no parent index */
+ InvalidOid, /* no parent constraint */
true, /* is_alter_table */
check_rights,
false, /* check_not_in_use - we did it already */
Assert(OidIsValid(index_oid));
Assert(stmt->isconstraint);
+ /*
+ * Doing this on partitioned tables is not a simple feature to implement,
+ * so let's punt for now.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("ALTER TABLE / ADD CONSTRAINT USING INDEX is not supported on partitioned tables")));
+
indexRel = index_open(index_oid, AccessShareLock);
indexName = pstrdup(RelationGetRelationName(indexRel));
address = index_constraint_create(rel,
index_oid,
+ InvalidOid,
indexInfo,
constraintName,
constraintType,
IndexInfo *info;
AttrNumber *attmap;
bool found = false;
+ Oid constraintOid;
/*
* Ignore indexes in the partitioned table other than partitioned
attmap = convert_tuples_by_name_map(RelationGetDescr(attachrel),
RelationGetDescr(rel),
gettext_noop("could not convert row type"));
+ constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(rel), idx);
/*
* Scan the list of existing indexes in the partition-to-be, and mark
*/
for (i = 0; i < list_length(attachRelIdxs); i++)
{
+ Oid cldConstrOid = InvalidOid;
+
/* does this index have a parent? if so, can't use it */
if (has_superclass(RelationGetRelid(attachrelIdxRels[i])))
continue;
attmap,
RelationGetDescr(rel)->natts))
{
+ /*
+ * If this index is being created in the parent because of a
+ * constraint, then the child needs to have a constraint also,
+ * so look for one. If there is no such constraint, this
+ * index is no good, so keep looking.
+ */
+ if (OidIsValid(constraintOid))
+ {
+ cldConstrOid =
+ get_relation_idx_constraint_oid(RelationGetRelid(attachrel),
+ RelationGetRelid(attachrelIdxRels[i]));
+ /* no dice */
+ if (!OidIsValid(cldConstrOid))
+ continue;
+ }
+
/* bingo. */
IndexSetParentIndex(attachrelIdxRels[i], idx);
+ if (OidIsValid(constraintOid))
+ ConstraintSetParentConstraint(cldConstrOid, constraintOid);
found = true;
break;
}
if (!found)
{
IndexStmt *stmt;
+ Oid constraintOid;
stmt = generateClonedIndexStmt(NULL, RelationGetRelid(attachrel),
idxRel, attmap,
- RelationGetDescr(rel)->natts);
+ RelationGetDescr(rel)->natts,
+ &constraintOid);
DefineIndex(RelationGetRelid(attachrel), stmt, InvalidOid,
RelationGetRelid(idxRel),
+ constraintOid,
false, false, false, false, false);
}
bool found;
int i;
PartitionDesc partDesc;
+ Oid constraintOid,
+ cldConstrId = InvalidOid;
/*
* If this partition already has an index attached, refuse the operation.
RelationGetRelationName(parentIdx)),
errdetail("The index definitions do not match.")));
+ /*
+ * If there is a constraint in the parent, make sure there is one
+ * in the child too.
+ */
+ constraintOid = get_relation_idx_constraint_oid(RelationGetRelid(parentTbl),
+ RelationGetRelid(parentIdx));
+
+ if (OidIsValid(constraintOid))
+ {
+ cldConstrId = get_relation_idx_constraint_oid(RelationGetRelid(partTbl),
+ partIdxId);
+ if (!OidIsValid(cldConstrId))
+ 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 \"%s\" belongs to a constraint in table \"%s\" but no constraint exists for index \"%s\".",
+ RelationGetRelationName(parentIdx),
+ RelationGetRelationName(parentTbl),
+ RelationGetRelationName(partIdx))));
+ }
+
/* All good -- do it */
IndexSetParentIndex(partIdx, RelationGetRelid(parentIdx));
+ if (OidIsValid(constraintOid))
+ ConstraintSetParentConstraint(cldConstrId, constraintOid);
+
pfree(attmap);
CommandCounterIncrement();
TargetEntry *te;
int attno;
+ if (targetrel->rd_partdesc)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("%s cannot be applied to partitioned table \"%s\"",
+ "ON CONFLICT DO UPDATE",
+ RelationGetRelationName(targetrel))));
+
/*
* All INSERT expressions have been parsed, get ready for potentially
* existing SET statements that need to be processed like an UPDATE.
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
- if (cxt->ispartitioned)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("primary key constraints are not supported on partitioned tables"),
- parser_errposition(cxt->pstate,
- constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
- if (cxt->ispartitioned)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("unique constraints are not supported on partitioned tables"),
- parser_errposition(cxt->pstate,
- constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
- if (cxt->ispartitioned)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("primary key constraints are not supported on partitioned tables"),
- parser_errposition(cxt->pstate,
- constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
- if (cxt->ispartitioned)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("unique constraints are not supported on partitioned tables"),
- parser_errposition(cxt->pstate,
- constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
/* Build CREATE INDEX statement to recreate the parent_index */
index_stmt = generateClonedIndexStmt(cxt->relation, InvalidOid,
parent_index,
- attmap, tupleDesc->natts);
+ attmap, tupleDesc->natts, NULL);
/* Copy comment on index, if requested */
if (table_like_clause->options & CREATE_TABLE_LIKE_COMMENTS)
*/
IndexStmt *
generateClonedIndexStmt(RangeVar *heapRel, Oid heapRelid, Relation source_idx,
- const AttrNumber *attmap, int attmap_length)
+ const AttrNumber *attmap, int attmap_length, Oid *constraintOid)
{
Oid source_relid = RelationGetRelid(source_idx);
HeapTuple ht_idxrel;
HeapTuple ht_constr;
Form_pg_constraint conrec;
+ if (constraintOid)
+ *constraintOid = constraintId;
+
ht_constr = SearchSysCache1(CONSTROID,
ObjectIdGetDatum(constraintId));
if (!HeapTupleIsValid(ht_constr))
stmt,
InvalidOid, /* no predefined OID */
InvalidOid, /* no parent index */
+ InvalidOid, /* no parent constraint */
false, /* is_alter_table */
true, /* check_rights */
true, /* check_not_in_use */
role => 1,
section_pre_data => 1, }, },
+ 'ALTER TABLE measurement PRIMARY KEY' => {
+ all_runs => 1,
+ catch_all => 'CREATE ... commands',
+ create_order => 93,
+ create_sql => 'ALTER TABLE dump_test.measurement ADD PRIMARY KEY (city_id, logdate);',
+ regexp => qr/^
+ \QALTER TABLE ONLY measurement\E \n^\s+
+ \QADD CONSTRAINT measurement_pkey PRIMARY KEY (city_id, logdate);\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',
section_pre_data => 1,
test_schema_plus_blobs => 1, }, },
+ 'ALTER INDEX ... ATTACH PARTITION (primary key)' => {
+ all_runs => 1,
+ catch_all => 'CREATE ... commands',
+ regexp => qr/^
+ \QALTER INDEX dump_test.measurement_pkey ATTACH PARTITION dump_test_second_schema.measurement_y2006m2_pkey\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',
const char *indexRelationName,
Oid indexRelationId,
Oid parentIndexRelid,
+ Oid parentConstraintId,
Oid relFileNode,
IndexInfo *indexInfo,
List *indexColNames,
bits16 flags,
bits16 constr_flags,
bool allow_system_table_mods,
- bool is_internal);
+ bool is_internal,
+ Oid *constraintId);
#define INDEX_CONSTR_CREATE_MARK_AS_PRIMARY (1 << 0)
#define INDEX_CONSTR_CREATE_DEFERRABLE (1 << 1)
extern ObjectAddress index_constraint_create(Relation heapRelation,
Oid indexRelationId,
+ Oid parentConstraintId,
IndexInfo *indexInfo,
const char *constraintName,
char constraintType,
extern void RemoveConstraintById(Oid conId);
extern void RenameConstraintById(Oid conId, const char *newname);
-extern void SetValidatedConstraintById(Oid conId);
extern bool ConstraintNameIsUsed(ConstraintCategory conCat, Oid objId,
Oid objNamespace, const char *conname);
extern void AlterConstraintNamespaces(Oid ownerId, Oid oldNspId,
Oid newNspId, bool isType, ObjectAddresses *objsMoved);
+extern void ConstraintSetParentConstraint(Oid childConstrId,
+ Oid parentConstrId);
extern Oid get_relation_constraint_oid(Oid relid, const char *conname, bool missing_ok);
extern Bitmapset *get_relation_constraint_attnos(Oid relid, const char *conname,
bool missing_ok, Oid *constraintOid);
extern Oid get_domain_constraint_oid(Oid typid, const char *conname, bool missing_ok);
+extern Oid get_relation_idx_constraint_oid(Oid relationId, Oid indexId);
extern Bitmapset *get_primary_key_attnos(Oid relid, bool deferrableOk,
Oid *constraintOid);
IndexStmt *stmt,
Oid indexRelationId,
Oid parentIndexId,
+ Oid parentConstraintId,
bool is_alter_table,
bool check_rights,
bool check_not_in_use,
PartitionBoundSpec *spec);
extern IndexStmt *generateClonedIndexStmt(RangeVar *heapRel, Oid heapOid,
Relation source_idx,
- const AttrNumber *attmap, int attmap_length);
+ const AttrNumber *attmap, int attmap_length,
+ Oid *constraintOid);
#endif /* PARSE_UTILCMD_H */
a int,
b int
) PARTITION BY RANGE (a, (a+b+1));
-ALTER TABLE partitioned ADD UNIQUE (a);
-ERROR: unique constraints are not supported on partitioned tables
-LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);
- ^
-ALTER TABLE partitioned ADD PRIMARY KEY (a);
-ERROR: primary key constraints are not supported on partitioned tables
-LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);
- ^
ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
ERROR: foreign key constraints are not supported on partitioned tables
LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
ERROR: cannot drop index cwi_replaced_pkey because constraint cwi_replaced_pkey on table cwi_test requires it
HINT: You can drop constraint cwi_replaced_pkey on table cwi_test instead.
DROP TABLE cwi_test;
+-- ADD CONSTRAINT USING INDEX is forbidden on partitioned tables
+CREATE TABLE cwi_test(a int) PARTITION BY hash (a);
+create unique index on cwi_test (a);
+alter table cwi_test add primary key using index cwi_test_a_idx ;
+ERROR: ALTER TABLE / ADD CONSTRAINT USING INDEX is not supported on partitioned tables
+DROP TABLE cwi_test;
--
-- Check handling of indexes on system columns
--
) PARTITION BY LIST (a1, a2); -- fail
ERROR: cannot use "list" partition strategy with more than one column
-- unsupported constraint type for partitioned tables
-CREATE TABLE partitioned (
- a int PRIMARY KEY
-) PARTITION BY RANGE (a);
-ERROR: primary key constraints are not supported on partitioned tables
-LINE 2: a int PRIMARY KEY
- ^
CREATE TABLE pkrel (
a int PRIMARY KEY
);
LINE 2: a int REFERENCES pkrel(a)
^
DROP TABLE pkrel;
-CREATE TABLE partitioned (
- a int UNIQUE
-) PARTITION BY RANGE (a);
-ERROR: unique constraints are not supported on partitioned tables
-LINE 2: a int UNIQUE
- ^
CREATE TABLE partitioned (
a int,
EXCLUDE USING gist (a WITH &&)
-- 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;
idxpart_col_keep_idx | col_keep | 1
(7 rows)
+drop table idxpart;
+--
+-- Constraint-related indexes
+--
+-- Verify that it works to add primary key / unique to partitioned tables
+create table idxpart (a int primary key, b int) partition by range (a);
+\d idxpart
+ Table "public.idxpart"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | not null |
+ b | integer | | |
+Partition key: RANGE (a)
+Indexes:
+ "idxpart_pkey" PRIMARY KEY, btree (a)
+Number of partitions: 0
+
+drop table idxpart;
+-- but not if you fail to use the full partition key
+create table idxpart (a int unique, b int) partition by range (a, b);
+ERROR: insufficient columns in UNIQUE constraint definition
+DETAIL: UNIQUE constraint on table "idxpart" lacks column "b" which is part of the partition key.
+create table idxpart (a int, b int unique) partition by range (a, b);
+ERROR: insufficient columns in UNIQUE constraint definition
+DETAIL: UNIQUE constraint on table "idxpart" lacks column "a" which is part of the partition key.
+create table idxpart (a int primary key, b int) partition by range (b, a);
+ERROR: insufficient columns in PRIMARY KEY constraint definition
+DETAIL: PRIMARY KEY constraint on table "idxpart" lacks column "b" which is part of the partition key.
+create table idxpart (a int, b int primary key) partition by range (b, a);
+ERROR: insufficient columns in PRIMARY KEY constraint definition
+DETAIL: PRIMARY KEY constraint on table "idxpart" lacks column "a" which is part of the partition key.
+-- OK if you use them in some other order
+create table idxpart (a int, b int, c text, primary key (a, b, c)) partition by range (b, c, a);
+drop table idxpart;
+-- not other types of index-based constraints
+create table idxpart (a int, exclude (a with = )) partition by range (a);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 1: create table idxpart (a int, exclude (a with = )) partition ...
+ ^
+-- no expressions in partition key for PK/UNIQUE
+create table idxpart (a int primary key, b int) partition by range ((b + a));
+ERROR: unsupported PRIMARY KEY constraint with partition key definition
+DETAIL: PRIMARY KEY constraints cannot be used when partition keys include expressions.
+create table idxpart (a int unique, b int) partition by range ((b + a));
+ERROR: unsupported UNIQUE constraint with partition key definition
+DETAIL: UNIQUE constraints cannot be used when partition keys include expressions.
+-- use ALTER TABLE to add a primary key
+create table idxpart (a int, b int, c text) partition by range (a, b);
+alter table idxpart add primary key (a); -- not an incomplete one though
+ERROR: insufficient columns in PRIMARY KEY constraint definition
+DETAIL: PRIMARY KEY constraint on table "idxpart" lacks column "b" which is part of the partition key.
+alter table idxpart add primary key (a, b); -- this works
+\d idxpart
+ Table "public.idxpart"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | not null |
+ b | integer | | not null |
+ c | text | | |
+Partition key: RANGE (a, b)
+Indexes:
+ "idxpart_pkey" PRIMARY KEY, btree (a, b)
+Number of partitions: 0
+
+create table idxpart1 partition of idxpart for values from (0, 0) to (1000, 1000);
+\d idxpart1
+ Table "public.idxpart1"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | not null |
+ b | integer | | not null |
+ c | text | | |
+Partition of: idxpart FOR VALUES FROM (0, 0) TO (1000, 1000)
+Indexes:
+ "idxpart1_pkey" PRIMARY KEY, btree (a, b)
+
+drop table idxpart;
+-- use ALTER TABLE to add a unique constraint
+create table idxpart (a int, b int) partition by range (a, b);
+alter table idxpart add unique (a); -- not an incomplete one though
+ERROR: insufficient columns in UNIQUE constraint definition
+DETAIL: UNIQUE constraint on table "idxpart" lacks column "b" which is part of the partition key.
+alter table idxpart add unique (b, a); -- this works
+\d idxpart
+ Table "public.idxpart"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+ b | integer | | |
+Partition key: RANGE (a, b)
+Indexes:
+ "idxpart_b_a_key" UNIQUE CONSTRAINT, btree (b, a)
+Number of partitions: 0
+
+drop table idxpart;
+-- Exclusion constraints cannot be added
+create table idxpart (a int, b int) partition by range (a);
+alter table idxpart add exclude (a with =);
+ERROR: exclusion constraints are not supported on partitioned tables
+LINE 1: alter table idxpart add exclude (a with =);
+ ^
+drop table idxpart;
+-- When (sub)partitions are created, they also contain the constraint
+create table idxpart (a int, b int, primary key (a, b)) partition by range (a, b);
+create table idxpart1 partition of idxpart for values from (1, 1) to (10, 10);
+create table idxpart2 partition of idxpart for values from (10, 10) to (20, 20)
+ partition by range (b);
+create table idxpart21 partition of idxpart2 for values from (10) to (15);
+create table idxpart22 partition of idxpart2 for values from (15) to (20);
+create table idxpart3 (b int not null, a int not null);
+alter table idxpart attach partition idxpart3 for values from (20, 20) to (30, 30);
+select conname, contype, conrelid::regclass, conindid::regclass, conkey
+ from pg_constraint where conrelid::regclass::text like 'idxpart%'
+ order by conname;
+ conname | contype | conrelid | conindid | conkey
+----------------+---------+-----------+----------------+--------
+ idxpart1_pkey | p | idxpart1 | idxpart1_pkey | {1,2}
+ idxpart21_pkey | p | idxpart21 | idxpart21_pkey | {1,2}
+ idxpart22_pkey | p | idxpart22 | idxpart22_pkey | {1,2}
+ idxpart2_pkey | p | idxpart2 | idxpart2_pkey | {1,2}
+ idxpart3_pkey | p | idxpart3 | idxpart3_pkey | {2,1}
+ idxpart_pkey | p | idxpart | idxpart_pkey | {1,2}
+(6 rows)
+
+drop table idxpart;
+-- Verify that multi-layer partitioning honors the requirement that all
+-- columns in the partition key must appear in primary key
+create table idxpart (a int, b int, primary key (a)) partition by range (a);
+create table idxpart2 partition of idxpart
+for values from (0) to (1000) partition by range (b); -- fail
+ERROR: insufficient columns in PRIMARY KEY constraint definition
+DETAIL: PRIMARY KEY constraint on table "idxpart2" lacks column "b" which is part of the partition key.
+drop table idxpart;
+-- Multi-layer partitioning works correctly in this case:
+create table idxpart (a int, b int, primary key (a, b)) partition by range (a);
+create table idxpart2 partition of idxpart for values from (0) to (1000) partition by range (b);
+create table idxpart21 partition of idxpart2 for values from (0) to (1000);
+select conname, contype, conrelid::regclass, conindid::regclass, conkey
+ from pg_constraint where conrelid::regclass::text like 'idxpart%'
+ order by conname;
+ conname | contype | conrelid | conindid | conkey
+----------------+---------+-----------+----------------+--------
+ idxpart21_pkey | p | idxpart21 | idxpart21_pkey | {1,2}
+ idxpart2_pkey | p | idxpart2 | idxpart2_pkey | {1,2}
+ idxpart_pkey | p | idxpart | idxpart_pkey | {1,2}
+(3 rows)
+
+drop table idxpart;
+-- If a partitioned table has a unique/PK constraint, then it's not possible
+-- to drop the corresponding constraint in the children; nor it's possible
+-- to drop the indexes individually. Dropping the constraint in the parent
+-- gets rid of the lot.
+create table idxpart (i int) partition by hash (i);
+create table idxpart0 partition of idxpart (i) for values with (modulus 2, remainder 0);
+create table idxpart1 partition of idxpart (i) for values with (modulus 2, remainder 1);
+alter table idxpart0 add primary key(i);
+alter table idxpart add primary key(i);
+select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid,
+ conname, conislocal, coninhcount, connoinherit, convalidated
+ from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid)
+ left join pg_constraint con on (idx.indexrelid = con.conindid)
+ where indrelid::regclass::text like 'idxpart%'
+ order by indexrelid::regclass::text collate "C";
+ indrelid | indexrelid | inhparent | indisvalid | conname | conislocal | coninhcount | connoinherit | convalidated
+----------+---------------+--------------+------------+---------------+------------+-------------+--------------+--------------
+ idxpart0 | idxpart0_pkey | idxpart_pkey | t | idxpart0_pkey | f | 1 | t | t
+ idxpart1 | idxpart1_pkey | idxpart_pkey | t | idxpart1_pkey | f | 1 | t | t
+ idxpart | idxpart_pkey | | t | idxpart_pkey | t | 0 | t | t
+(3 rows)
+
+drop index idxpart0_pkey; -- fail
+ERROR: cannot drop index idxpart0_pkey because index idxpart_pkey requires it
+HINT: You can drop index idxpart_pkey instead.
+drop index idxpart1_pkey; -- fail
+ERROR: cannot drop index idxpart1_pkey because index idxpart_pkey requires it
+HINT: You can drop index idxpart_pkey instead.
+alter table idxpart0 drop constraint idxpart0_pkey; -- fail
+ERROR: cannot drop inherited constraint "idxpart0_pkey" of relation "idxpart0"
+alter table idxpart1 drop constraint idxpart1_pkey; -- fail
+ERROR: cannot drop inherited constraint "idxpart1_pkey" of relation "idxpart1"
+alter table idxpart drop constraint idxpart_pkey; -- ok
+select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid,
+ conname, conislocal, coninhcount, connoinherit, convalidated
+ from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid)
+ left join pg_constraint con on (idx.indexrelid = con.conindid)
+ where indrelid::regclass::text like 'idxpart%'
+ order by indexrelid::regclass::text collate "C";
+ indrelid | indexrelid | inhparent | indisvalid | conname | conislocal | coninhcount | connoinherit | convalidated
+----------+------------+-----------+------------+---------+------------+-------------+--------------+--------------
+(0 rows)
+
+drop table idxpart;
+-- If a partitioned table has a constraint whose index is not valid,
+-- attaching a missing partition makes it valid.
+create table idxpart (a int) partition by range (a);
+create table idxpart0 (like idxpart);
+alter table idxpart0 add primary key (a);
+alter table idxpart attach partition idxpart0 for values from (0) to (1000);
+alter table only idxpart add primary key (a);
+select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid,
+ conname, conislocal, coninhcount, connoinherit, convalidated
+ from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid)
+ left join pg_constraint con on (idx.indexrelid = con.conindid)
+ where indrelid::regclass::text like 'idxpart%'
+ order by indexrelid::regclass::text collate "C";
+ indrelid | indexrelid | inhparent | indisvalid | conname | conislocal | coninhcount | connoinherit | convalidated
+----------+---------------+-----------+------------+---------------+------------+-------------+--------------+--------------
+ idxpart0 | idxpart0_pkey | | t | idxpart0_pkey | t | 0 | t | t
+ idxpart | idxpart_pkey | | f | idxpart_pkey | t | 0 | t | t
+(2 rows)
+
+alter index idxpart_pkey attach partition idxpart0_pkey;
+select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid,
+ conname, conislocal, coninhcount, connoinherit, convalidated
+ from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid)
+ left join pg_constraint con on (idx.indexrelid = con.conindid)
+ where indrelid::regclass::text like 'idxpart%'
+ order by indexrelid::regclass::text collate "C";
+ indrelid | indexrelid | inhparent | indisvalid | conname | conislocal | coninhcount | connoinherit | convalidated
+----------+---------------+--------------+------------+---------------+------------+-------------+--------------+--------------
+ idxpart0 | idxpart0_pkey | idxpart_pkey | t | idxpart0_pkey | f | 1 | t | t
+ idxpart | idxpart_pkey | | t | idxpart_pkey | t | 0 | t | t
+(2 rows)
+
+drop table idxpart;
+-- if a partition has a unique index without a constraint, does not attach
+-- automatically; creates a new index instead.
+create table idxpart (a int, b int) partition by range (a);
+create table idxpart1 (a int not null, b int);
+create unique index on idxpart1 (a);
+alter table idxpart add primary key (a);
+alter table idxpart attach partition idxpart1 for values from (1) to (1000);
+select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid,
+ conname, conislocal, coninhcount, connoinherit, convalidated
+ from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid)
+ left join pg_constraint con on (idx.indexrelid = con.conindid)
+ where indrelid::regclass::text like 'idxpart%'
+ order by indexrelid::regclass::text collate "C";
+ indrelid | indexrelid | inhparent | indisvalid | conname | conislocal | coninhcount | connoinherit | convalidated
+----------+----------------+--------------+------------+---------------+------------+-------------+--------------+--------------
+ idxpart1 | idxpart1_a_idx | | t | | | | |
+ idxpart1 | idxpart1_pkey | idxpart_pkey | t | idxpart1_pkey | f | 1 | t | t
+ idxpart | idxpart_pkey | | t | idxpart_pkey | t | 0 | t | t
+(3 rows)
+
+drop table idxpart;
+-- Can't attach an index without a corresponding constraint
+create table idxpart (a int, b int) partition by range (a);
+create table idxpart1 (a int not null, b int);
+create unique index on idxpart1 (a);
+alter table idxpart attach partition idxpart1 for values from (1) to (1000);
+alter table only idxpart add primary key (a);
+alter index idxpart_pkey attach partition idxpart1_a_idx; -- fail
+ERROR: cannot attach index "idxpart1_a_idx" as a partition of index "idxpart_pkey"
+DETAIL: The index "idxpart_pkey" belongs to a constraint in table "idxpart" but no constraint exists for index "idxpart1_a_idx".
+drop table idxpart;
+-- Test that unique constraints are working
+create table idxpart (a int, b text, primary key (a, b)) partition by range (a);
+create table idxpart1 partition of idxpart for values from (0) to (100000);
+create table idxpart2 (c int, like idxpart);
+insert into idxpart2 (c, a, b) values (42, 572814, 'inserted first');
+alter table idxpart2 drop column c;
+create unique index on idxpart (a);
+alter table idxpart attach partition idxpart2 for values from (100000) to (1000000);
+insert into idxpart values (0, 'zero'), (42, 'life'), (2^16, 'sixteen');
+insert into idxpart select 2^g, format('two to power of %s', g) from generate_series(15, 17) g;
+ERROR: duplicate key value violates unique constraint "idxpart1_a_idx"
+DETAIL: Key (a)=(65536) already exists.
+insert into idxpart values (16, 'sixteen');
+insert into idxpart (b, a) values ('one', 142857), ('two', 285714);
+insert into idxpart select a * 2, b || b from idxpart where a between 2^16 and 2^19;
+ERROR: duplicate key value violates unique constraint "idxpart2_a_idx"
+DETAIL: Key (a)=(285714) already exists.
+insert into idxpart values (572814, 'five');
+ERROR: duplicate key value violates unique constraint "idxpart2_a_idx"
+DETAIL: Key (a)=(572814) already exists.
+insert into idxpart values (857142, 'six');
+select tableoid::regclass, * from idxpart order by a;
+ tableoid | a | b
+----------+--------+----------------
+ idxpart1 | 0 | zero
+ idxpart1 | 16 | sixteen
+ idxpart1 | 42 | life
+ idxpart1 | 65536 | sixteen
+ idxpart2 | 142857 | one
+ idxpart2 | 285714 | two
+ idxpart2 | 572814 | inserted first
+ idxpart2 | 857142 | six
+(8 rows)
+
drop table idxpart;
-- intentionally leave some objects around
create table idxpart (a int) partition by range (a);
create index on only idxpart2 (a);
alter index idxpart2_a_idx attach partition idxpart22_a_idx;
create index on idxpart (a);
+create table idxpart_another (a int, b int, primary key (a, b)) partition by range (a);
+create table idxpart_another_1 partition of idxpart_another for values from (0) to (100);
insert into parted_conflict_test values (1, 'a') on conflict do nothing;
-- however, on conflict do update is not supported yet
insert into parted_conflict_test values (1) on conflict (b) do update set a = excluded.a;
-ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification
+ERROR: ON CONFLICT DO UPDATE cannot be applied to partitioned table "parted_conflict_test"
-- but it works OK if we target the partition directly
insert into parted_conflict_test_1 values (1) on conflict (b) do
update set a = excluded.a;
a int,
b int
) PARTITION BY RANGE (a, (a+b+1));
-ALTER TABLE partitioned ADD UNIQUE (a);
-ALTER TABLE partitioned ADD PRIMARY KEY (a);
ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
DROP TABLE cwi_test;
+-- ADD CONSTRAINT USING INDEX is forbidden on partitioned tables
+CREATE TABLE cwi_test(a int) PARTITION BY hash (a);
+create unique index on cwi_test (a);
+alter table cwi_test add primary key using index cwi_test_a_idx ;
+DROP TABLE cwi_test;
+
--
-- Check handling of indexes on system columns
--
) PARTITION BY LIST (a1, a2); -- fail
-- unsupported constraint type for partitioned tables
-CREATE TABLE partitioned (
- a int PRIMARY KEY
-) PARTITION BY RANGE (a);
-
CREATE TABLE pkrel (
a int PRIMARY KEY
);
) PARTITION BY RANGE (a);
DROP TABLE pkrel;
-CREATE TABLE partitioned (
- a int UNIQUE
-) PARTITION BY RANGE (a);
-
CREATE TABLE partitioned (
a int,
EXCLUDE USING gist (a WITH &&)
-- 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;
order by attrelid::regclass, attnum;
drop table idxpart;
+--
+-- Constraint-related indexes
+--
+
+-- Verify that it works to add primary key / unique to partitioned tables
+create table idxpart (a int primary key, b int) partition by range (a);
+\d idxpart
+drop table idxpart;
+
+-- but not if you fail to use the full partition key
+create table idxpart (a int unique, b int) partition by range (a, b);
+create table idxpart (a int, b int unique) partition by range (a, b);
+create table idxpart (a int primary key, b int) partition by range (b, a);
+create table idxpart (a int, b int primary key) partition by range (b, a);
+
+-- OK if you use them in some other order
+create table idxpart (a int, b int, c text, primary key (a, b, c)) partition by range (b, c, a);
+drop table idxpart;
+
+-- not other types of index-based constraints
+create table idxpart (a int, exclude (a with = )) partition by range (a);
+
+-- no expressions in partition key for PK/UNIQUE
+create table idxpart (a int primary key, b int) partition by range ((b + a));
+create table idxpart (a int unique, b int) partition by range ((b + a));
+
+-- use ALTER TABLE to add a primary key
+create table idxpart (a int, b int, c text) partition by range (a, b);
+alter table idxpart add primary key (a); -- not an incomplete one though
+alter table idxpart add primary key (a, b); -- this works
+\d idxpart
+create table idxpart1 partition of idxpart for values from (0, 0) to (1000, 1000);
+\d idxpart1
+drop table idxpart;
+
+-- use ALTER TABLE to add a unique constraint
+create table idxpart (a int, b int) partition by range (a, b);
+alter table idxpart add unique (a); -- not an incomplete one though
+alter table idxpart add unique (b, a); -- this works
+\d idxpart
+drop table idxpart;
+
+-- Exclusion constraints cannot be added
+create table idxpart (a int, b int) partition by range (a);
+alter table idxpart add exclude (a with =);
+drop table idxpart;
+
+-- When (sub)partitions are created, they also contain the constraint
+create table idxpart (a int, b int, primary key (a, b)) partition by range (a, b);
+create table idxpart1 partition of idxpart for values from (1, 1) to (10, 10);
+create table idxpart2 partition of idxpart for values from (10, 10) to (20, 20)
+ partition by range (b);
+create table idxpart21 partition of idxpart2 for values from (10) to (15);
+create table idxpart22 partition of idxpart2 for values from (15) to (20);
+create table idxpart3 (b int not null, a int not null);
+alter table idxpart attach partition idxpart3 for values from (20, 20) to (30, 30);
+select conname, contype, conrelid::regclass, conindid::regclass, conkey
+ from pg_constraint where conrelid::regclass::text like 'idxpart%'
+ order by conname;
+drop table idxpart;
+
+-- Verify that multi-layer partitioning honors the requirement that all
+-- columns in the partition key must appear in primary key
+create table idxpart (a int, b int, primary key (a)) partition by range (a);
+create table idxpart2 partition of idxpart
+for values from (0) to (1000) partition by range (b); -- fail
+drop table idxpart;
+
+-- Multi-layer partitioning works correctly in this case:
+create table idxpart (a int, b int, primary key (a, b)) partition by range (a);
+create table idxpart2 partition of idxpart for values from (0) to (1000) partition by range (b);
+create table idxpart21 partition of idxpart2 for values from (0) to (1000);
+select conname, contype, conrelid::regclass, conindid::regclass, conkey
+ from pg_constraint where conrelid::regclass::text like 'idxpart%'
+ order by conname;
+drop table idxpart;
+
+-- If a partitioned table has a unique/PK constraint, then it's not possible
+-- to drop the corresponding constraint in the children; nor it's possible
+-- to drop the indexes individually. Dropping the constraint in the parent
+-- gets rid of the lot.
+create table idxpart (i int) partition by hash (i);
+create table idxpart0 partition of idxpart (i) for values with (modulus 2, remainder 0);
+create table idxpart1 partition of idxpart (i) for values with (modulus 2, remainder 1);
+alter table idxpart0 add primary key(i);
+alter table idxpart add primary key(i);
+select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid,
+ conname, conislocal, coninhcount, connoinherit, convalidated
+ from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid)
+ left join pg_constraint con on (idx.indexrelid = con.conindid)
+ where indrelid::regclass::text like 'idxpart%'
+ order by indexrelid::regclass::text collate "C";
+drop index idxpart0_pkey; -- fail
+drop index idxpart1_pkey; -- fail
+alter table idxpart0 drop constraint idxpart0_pkey; -- fail
+alter table idxpart1 drop constraint idxpart1_pkey; -- fail
+alter table idxpart drop constraint idxpart_pkey; -- ok
+select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid,
+ conname, conislocal, coninhcount, connoinherit, convalidated
+ from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid)
+ left join pg_constraint con on (idx.indexrelid = con.conindid)
+ where indrelid::regclass::text like 'idxpart%'
+ order by indexrelid::regclass::text collate "C";
+drop table idxpart;
+
+-- If a partitioned table has a constraint whose index is not valid,
+-- attaching a missing partition makes it valid.
+create table idxpart (a int) partition by range (a);
+create table idxpart0 (like idxpart);
+alter table idxpart0 add primary key (a);
+alter table idxpart attach partition idxpart0 for values from (0) to (1000);
+alter table only idxpart add primary key (a);
+select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid,
+ conname, conislocal, coninhcount, connoinherit, convalidated
+ from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid)
+ left join pg_constraint con on (idx.indexrelid = con.conindid)
+ where indrelid::regclass::text like 'idxpart%'
+ order by indexrelid::regclass::text collate "C";
+alter index idxpart_pkey attach partition idxpart0_pkey;
+select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid,
+ conname, conislocal, coninhcount, connoinherit, convalidated
+ from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid)
+ left join pg_constraint con on (idx.indexrelid = con.conindid)
+ where indrelid::regclass::text like 'idxpart%'
+ order by indexrelid::regclass::text collate "C";
+drop table idxpart;
+
+-- if a partition has a unique index without a constraint, does not attach
+-- automatically; creates a new index instead.
+create table idxpart (a int, b int) partition by range (a);
+create table idxpart1 (a int not null, b int);
+create unique index on idxpart1 (a);
+alter table idxpart add primary key (a);
+alter table idxpart attach partition idxpart1 for values from (1) to (1000);
+select indrelid::regclass, indexrelid::regclass, inhparent::regclass, indisvalid,
+ conname, conislocal, coninhcount, connoinherit, convalidated
+ from pg_index idx left join pg_inherits inh on (idx.indexrelid = inh.inhrelid)
+ left join pg_constraint con on (idx.indexrelid = con.conindid)
+ where indrelid::regclass::text like 'idxpart%'
+ order by indexrelid::regclass::text collate "C";
+drop table idxpart;
+
+-- Can't attach an index without a corresponding constraint
+create table idxpart (a int, b int) partition by range (a);
+create table idxpart1 (a int not null, b int);
+create unique index on idxpart1 (a);
+alter table idxpart attach partition idxpart1 for values from (1) to (1000);
+alter table only idxpart add primary key (a);
+alter index idxpart_pkey attach partition idxpart1_a_idx; -- fail
+drop table idxpart;
+
+-- Test that unique constraints are working
+create table idxpart (a int, b text, primary key (a, b)) partition by range (a);
+create table idxpart1 partition of idxpart for values from (0) to (100000);
+create table idxpart2 (c int, like idxpart);
+insert into idxpart2 (c, a, b) values (42, 572814, 'inserted first');
+alter table idxpart2 drop column c;
+create unique index on idxpart (a);
+alter table idxpart attach partition idxpart2 for values from (100000) to (1000000);
+insert into idxpart values (0, 'zero'), (42, 'life'), (2^16, 'sixteen');
+insert into idxpart select 2^g, format('two to power of %s', g) from generate_series(15, 17) g;
+insert into idxpart values (16, 'sixteen');
+insert into idxpart (b, a) values ('one', 142857), ('two', 285714);
+insert into idxpart select a * 2, b || b from idxpart where a between 2^16 and 2^19;
+insert into idxpart values (572814, 'five');
+insert into idxpart values (857142, 'six');
+select tableoid::regclass, * from idxpart order by a;
+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 index on only idxpart2 (a);
alter index idxpart2_a_idx attach partition idxpart22_a_idx;
create index on idxpart (a);
+create table idxpart_another (a int, b int, primary key (a, b)) partition by range (a);
+create table idxpart_another_1 partition of idxpart_another for values from (0) to (100);