From: Alvaro Herrera <alvherre@alvh.no-ip.org>
To: Robert Haas <robertmhaas@gmail.com>
Cc: Pg Hackers <pgsql-hackers@postgresql.org>
Cc: Maksim Milyutin <milyutinma@gmail.com>
Subject: Re: [HACKERS] Proposal: Local indexes for partitioned table
Date: Mon, 13 Nov 2017 18:55:35 +0100
Message-ID: <20171113175535.bvlfra6lm5rnoja2@alvherre.pgsql> (raw)
In-Reply-To: <20171113170646.gzweigyrgg6pwsg4@alvherre.pgsql>
Somehow I managed to include an unrelated patch as attachment. Here's
another attempt (on which I also lightly touched ddl.sgml with relevant
changes).
--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
From a485e8710110659d76cb377469a5eee75c64fcd0 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 16 Oct 2017 12:01:12 +0200
Subject: [PATCH v4 1/5] Simplify index_[constraint_]create API
Instead of passing large swaths of boolean arguments, define some flags
that can be used in a bitmask. This makes it easier not only to figure
out what each call site is doing, but also to add additional behavioral
flags.
The flags are split in two -- one set for index_create directly and
another for constraints. index_create() itself receives both, and then
passes down the latter to index_constraint_create, which can also be
called standalone.
Discussion: https://postgr.es/m/20171023151251.j75uoe27gajdjmlm@alvherre.pgsql
---
src/backend/catalog/index.c | 101 +++++++++++++++++++--------------------
src/backend/catalog/toasting.c | 3 +-
src/backend/commands/indexcmds.c | 33 +++++++++----
src/backend/commands/tablecmds.c | 13 +++--
src/include/catalog/index.h | 29 ++++++-----
5 files changed, 100 insertions(+), 79 deletions(-)
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.cindex c7b2f031f0..17faeffada 100644--- a/src/backend/catalog/index.c+++ b/src/backend/catalog/index.c@@ -680,19 +680,25 @@ UpdateIndexRelation(Oid indexoid,
* classObjectId: array of index opclass OIDs, one per index column
* coloptions: array of per-index-column indoption settings
* reloptions: AM-specific options
- * isprimary: index is a PRIMARY KEY- * isconstraint: index is owned by PRIMARY KEY, UNIQUE, or EXCLUSION constraint- * deferrable: constraint is DEFERRABLE- * initdeferred: constraint is INITIALLY DEFERRED+ * flags: bitmask that can include any combination of these bits:+ * INDEX_CREATE_IS_PRIMARY+ * the index is a primary key+ * INDEX_CREATE_ADD_CONSTRAINT:+ * invoke index_constraint_create also+ * INDEX_CREATE_SKIP_BUILD:+ * skip the index_build() step for the moment; caller must do it+ * later (typically via reindex_index())+ * INDEX_CREATE_CONCURRENT:+ * do not lock the table against writers. The index will be+ * marked "invalid" and the caller must take additional steps+ * to fix it up.+ * INDEX_CREATE_IF_NOT_EXISTS:+ * do not throw an error if a relation with the same name+ * already exists.+ * 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
- * skip_build: true to skip the index_build() step for the moment; caller- * must do it later (typically via reindex_index())- * concurrent: if true, do not lock the table against writers. The index- * will be marked "invalid" and the caller must take additional steps- * to fix it up.
* is_internal: if true, post creation hook for new index
- * if_not_exists: if true, do not throw an error if a relation with- * the same name already exists.
*
* Returns the OID of the created index.
*/
@@ -709,15 +715,10 @@ index_create(Relation heapRelation,
Oid *classObjectId,
int16 *coloptions,
Datum reloptions,
- bool isprimary,- bool isconstraint,- bool deferrable,- bool initdeferred,+ uint16 flags,+ uint16 constr_flags,
bool allow_system_table_mods,
- bool skip_build,- bool concurrent,- bool is_internal,- bool if_not_exists)+ bool is_internal)
{
Oid heapRelationId = RelationGetRelid(heapRelation);
Relation pg_class;
@@ -729,6 +730,12 @@ index_create(Relation heapRelation,
Oid namespaceId;
int i;
char relpersistence;
+ bool isprimary = flags & INDEX_CREATE_IS_PRIMARY;+ bool concurrent = flags & INDEX_CREATE_CONCURRENT;++ /* constraint flags can only be set when constraint is requested */+ Assert((constr_flags == 0) ||+ ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0));
is_exclusion = (indexInfo->ii_ExclusionOps != NULL);
@@ -794,7 +801,7 @@ index_create(Relation heapRelation,
if (get_relname_relid(indexRelationName, namespaceId))
{
- if (if_not_exists)+ if ((flags & INDEX_CREATE_IF_NOT_EXISTS) != 0)
{
ereport(NOTICE,
(errcode(ERRCODE_DUPLICATE_TABLE),
@@ -917,7 +924,7 @@ index_create(Relation heapRelation,
UpdateIndexRelation(indexRelationId, heapRelationId, indexInfo,
collationObjectId, classObjectId, coloptions,
isprimary, is_exclusion,
- !deferrable,+ (constr_flags & INDEX_CONSTR_CREATE_DEFERRABLE) == 0,
!concurrent);
/*
@@ -943,7 +950,7 @@ index_create(Relation heapRelation,
myself.objectId = indexRelationId;
myself.objectSubId = 0;
- if (isconstraint)+ if ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0)
{
char constraintType;
@@ -964,11 +971,7 @@ index_create(Relation heapRelation,
indexInfo,
indexRelationName,
constraintType,
- deferrable,- initdeferred,- false, /* already marked primary */- false, /* pg_index entry is OK */- false, /* no old dependencies */+ constr_flags,
allow_system_table_mods,
is_internal);
}
@@ -1005,10 +1008,6 @@ index_create(Relation heapRelation,
recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
}
-- /* Non-constraint indexes can't be deferrable */- Assert(!deferrable);- Assert(!initdeferred);
}
/* Store dependency on collations */
@@ -1059,9 +1058,7 @@ index_create(Relation heapRelation,
else
{
/* Bootstrap mode - assert we weren't asked for constraint support */
- Assert(!isconstraint);- Assert(!deferrable);- Assert(!initdeferred);+ Assert((flags & INDEX_CREATE_ADD_CONSTRAINT) == 0);
}
/* Post creation hook for new index */
@@ -1089,15 +1086,16 @@ index_create(Relation heapRelation,
* If this is bootstrap (initdb) time, then we don't actually fill in the
* index yet. We'll be creating more indexes and classes later, so we
* delay filling them in until just before we're done with bootstrapping.
- * Similarly, if the caller specified skip_build then filling the index is- * delayed till later (ALTER TABLE can save work in some cases with this).- * Otherwise, we call the AM routine that constructs the index.+ * Similarly, if the caller specified to skip the build then filling the+ * index is delayed till later (ALTER TABLE can save work in some cases+ * with this). Otherwise, we call the AM routine that constructs the+ * index.
*/
if (IsBootstrapProcessingMode())
{
index_register(heapRelationId, indexRelationId, indexInfo);
}
- else if (skip_build)+ else if ((flags & INDEX_CREATE_SKIP_BUILD) != 0)
{
/*
* Caller is responsible for filling the index later on. However,
@@ -1137,12 +1135,13 @@ index_create(Relation heapRelation,
* constraintName: what it say (generally, should match name of index)
* constraintType: one of CONSTRAINT_PRIMARY, CONSTRAINT_UNIQUE, or
* CONSTRAINT_EXCLUSION
- * deferrable: constraint is DEFERRABLE- * initdeferred: constraint is INITIALLY DEFERRED- * mark_as_primary: if true, set flags to mark index as primary key- * update_pgindex: if true, update pg_index row (else caller's done that)- * remove_old_dependencies: if true, remove existing dependencies of index- * on table's columns+ * flags: bitmask that can include any combination of these bits:+ * INDEX_CONSTR_CREATE_MARK_AS_PRIMARY: index is a PRIMARY KEY+ * INDEX_CONSTR_CREATE_DEFERRABLE: constraint is DEFERRABLE+ * INDEX_CONSTR_CREATE_INIT_DEFERRED: constraint is INITIALLY DEFERRED+ * INDEX_CONSTR_CREATE_UPDATE_INDEX: update the pg_index row+ * INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS: remove existing dependencies+ * of index on table's columns
* allow_system_table_mods: allow table to be a system catalog
* is_internal: index is constructed due to internal process
*/
@@ -1152,11 +1151,7 @@ index_constraint_create(Relation heapRelation,
IndexInfo *indexInfo,
const char *constraintName,
char constraintType,
- bool deferrable,- bool initdeferred,- bool mark_as_primary,- bool update_pgindex,- bool remove_old_dependencies,+ uint16 constr_flags,
bool allow_system_table_mods,
bool is_internal)
{
@@ -1164,6 +1159,9 @@ index_constraint_create(Relation heapRelation,
ObjectAddress myself,
referenced;
Oid conOid;
+ bool deferrable = constr_flags & INDEX_CONSTR_CREATE_DEFERRABLE;+ bool initdeferred = constr_flags & INDEX_CONSTR_CREATE_INIT_DEFERRED;+ bool mark_as_primary = constr_flags & INDEX_CONSTR_CREATE_MARK_AS_PRIMARY;
/* constraint creation support doesn't work while bootstrapping */
Assert(!IsBootstrapProcessingMode());
@@ -1190,7 +1188,7 @@ index_constraint_create(Relation heapRelation,
* has any expressions or predicate, but we'd never be turning such an
* index into a UNIQUE or PRIMARY KEY constraint.
*/
- if (remove_old_dependencies)+ if (constr_flags & INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS)
deleteDependencyRecordsForClass(RelationRelationId, indexRelationId,
RelationRelationId, DEPENDENCY_AUTO);
@@ -1295,7 +1293,8 @@ index_constraint_create(Relation heapRelation,
* is a risk that concurrent readers of the table will miss seeing this
* index at all.
*/
- if (update_pgindex && (mark_as_primary || deferrable))+ if ((constr_flags & INDEX_CONSTR_CREATE_UPDATE_INDEX) &&+ (mark_as_primary || deferrable))
{
Relation pg_index;
HeapTuple indexTuple;
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.cindex 6f517bbcda..539ca79ad3 100644--- a/src/backend/catalog/toasting.c+++ b/src/backend/catalog/toasting.c@@ -333,8 +333,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
BTREE_AM_OID,
rel->rd_rel->reltablespace,
collationObjectId, classObjectId, coloptions, (Datum) 0,
- true, false, false, false,- true, false, false, true, false);+ INDEX_CREATE_IS_PRIMARY, 0, true, true);
heap_close(toast_rel, NoLock);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.cindex 89114af119..963cfedcb4 100644--- a/src/backend/commands/indexcmds.c+++ b/src/backend/commands/indexcmds.c@@ -333,6 +333,8 @@ DefineIndex(Oid relationId,
Datum reloptions;
int16 *coloptions;
IndexInfo *indexInfo;
+ uint16 flags;+ uint16 constr_flags;
int numberOfAttributes;
TransactionId limitXmin;
VirtualTransactionId *old_snapshots;
@@ -661,20 +663,35 @@ DefineIndex(Oid relationId,
Assert(!OidIsValid(stmt->oldNode) || (skip_build && !stmt->concurrent));
/*
- * Make the catalog entries for the index, including constraints. Then, if- * not skip_build || concurrent, actually build the index.+ * 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.
*/
+ flags = constr_flags = 0;+ if (stmt->isconstraint)+ flags |= INDEX_CREATE_ADD_CONSTRAINT;+ if (skip_build || stmt->concurrent)+ flags |= INDEX_CREATE_SKIP_BUILD;+ if (stmt->if_not_exists)+ flags |= INDEX_CREATE_IF_NOT_EXISTS;+ if (stmt->concurrent)+ flags |= INDEX_CREATE_CONCURRENT;+ if (stmt->primary)+ flags |= INDEX_CREATE_IS_PRIMARY;++ if (stmt->deferrable)+ constr_flags |= INDEX_CONSTR_CREATE_DEFERRABLE;+ if (stmt->initdeferred)+ constr_flags |= INDEX_CONSTR_CREATE_INIT_DEFERRED;+
indexRelationId =
index_create(rel, indexRelationName, indexRelationId, stmt->oldNode,
indexInfo, indexColNames,
accessMethodId, tablespaceId,
collationObjectId, classObjectId,
- coloptions, reloptions, stmt->primary,- stmt->isconstraint, stmt->deferrable, stmt->initdeferred,- allowSystemTableMods,- skip_build || stmt->concurrent,- stmt->concurrent, !check_rights,- stmt->if_not_exists);+ coloptions, reloptions,+ flags, constr_flags,+ allowSystemTableMods, !check_rights);
ObjectAddressSet(address, RelationRelationId, indexRelationId);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.cindex 9c66aa75ed..a723d98666 100644--- a/src/backend/commands/tablecmds.c+++ b/src/backend/commands/tablecmds.c@@ -6836,6 +6836,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
char *constraintName;
char constraintType;
ObjectAddress address;
+ uint16 flags;
Assert(IsA(stmt, IndexStmt));
Assert(OidIsValid(index_oid));
@@ -6880,16 +6881,18 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
constraintType = CONSTRAINT_UNIQUE;
/* Create the catalog entries for the constraint */
+ flags = INDEX_CONSTR_CREATE_UPDATE_INDEX |+ INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS |+ (stmt->initdeferred ? INDEX_CONSTR_CREATE_INIT_DEFERRED : 0) |+ (stmt->deferrable ? INDEX_CONSTR_CREATE_DEFERRABLE : 0) |+ (stmt->primary ? INDEX_CONSTR_CREATE_MARK_AS_PRIMARY : 0);+
address = index_constraint_create(rel,
index_oid,
indexInfo,
constraintName,
constraintType,
- stmt->deferrable,- stmt->initdeferred,- stmt->primary,- true, /* update pg_index */- true, /* remove old dependencies */+ flags,
allowSystemTableMods,
false); /* is_internal */
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.hindex 1d4ec09f8f..ab1f9ef1bd 100644--- a/src/include/catalog/index.h+++ b/src/include/catalog/index.h@@ -42,6 +42,12 @@ extern void index_check_primary_key(Relation heapRel,
IndexInfo *indexInfo,
bool is_alter_table);
+#define INDEX_CREATE_IS_PRIMARY (1 << 0)+#define INDEX_CREATE_ADD_CONSTRAINT (1 << 1)+#define INDEX_CREATE_SKIP_BUILD (1 << 2)+#define INDEX_CREATE_CONCURRENT (1 << 3)+#define INDEX_CREATE_IF_NOT_EXISTS (1 << 4)+
extern Oid index_create(Relation heapRelation,
const char *indexRelationName,
Oid indexRelationId,
@@ -54,26 +60,23 @@ extern Oid index_create(Relation heapRelation,
Oid *classObjectId,
int16 *coloptions,
Datum reloptions,
- bool isprimary,- bool isconstraint,- bool deferrable,- bool initdeferred,+ uint16 flags,+ uint16 constr_flags,
bool allow_system_table_mods,
- bool skip_build,- bool concurrent,- bool is_internal,- bool if_not_exists);+ bool is_internal);++#define INDEX_CONSTR_CREATE_MARK_AS_PRIMARY (1 << 0)+#define INDEX_CONSTR_CREATE_DEFERRABLE (1 << 1)+#define INDEX_CONSTR_CREATE_INIT_DEFERRED (1 << 2)+#define INDEX_CONSTR_CREATE_UPDATE_INDEX (1 << 3)+#define INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS (1 << 4)
extern ObjectAddress index_constraint_create(Relation heapRelation,
Oid indexRelationId,
IndexInfo *indexInfo,
const char *constraintName,
char constraintType,
- bool deferrable,- bool initdeferred,- bool mark_as_primary,- bool update_pgindex,- bool remove_old_dependencies,+ uint16 constr_flags,
bool allow_system_table_mods,
bool is_internal);
--
2.11.0
From 149e7e7bd3e68c2b2305df39189a193c3f29e262 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 26 Oct 2017 11:15:39 +0200
Subject: [PATCH v4 2/5] Get rid of copy_partition_key
Instead, allocate the objects in a temporary context right from the
start, and only make it permanent once no errors can occur anymore.
Reviewed-by: Robert Haas, Tom Lane
Discussion: https://postgr.es/m/20171027172730.eh2domlkpn4ja62m@alvherre.pgsql
---
src/backend/utils/cache/relcache.c | 69 +++++---------------------------------
1 file changed, 8 insertions(+), 61 deletions(-)
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.cindex 1908420d82..59280cd8bb 100644--- a/src/backend/utils/cache/relcache.c+++ b/src/backend/utils/cache/relcache.c@@ -262,7 +262,6 @@ static Relation AllocateRelationDesc(Form_pg_class relp);
static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
static void RelationBuildTupleDesc(Relation relation);
static void RelationBuildPartitionKey(Relation relation);
-static PartitionKey copy_partition_key(PartitionKey fromkey);
static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
static void RelationInitPhysicalAddr(Relation relation);
static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -846,6 +845,11 @@ RelationBuildPartitionKey(Relation relation)
if (!HeapTupleIsValid(tuple))
return;
+ partkeycxt = AllocSetContextCreate(CurTransactionContext,+ RelationGetRelationName(relation),+ ALLOCSET_SMALL_SIZES);+ oldcxt = MemoryContextSwitchTo(partkeycxt);+
key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
/* Fixed-length attributes */
@@ -983,71 +987,14 @@ RelationBuildPartitionKey(Relation relation)
ReleaseSysCache(tuple);
- /* Success --- now copy to the cache memory */- partkeycxt = AllocSetContextCreate(CacheMemoryContext,- RelationGetRelationName(relation),- ALLOCSET_SMALL_SIZES);+ /* Success --- make the relcache point to the newly constructed key */+ MemoryContextSetParent(partkeycxt, CacheMemoryContext);
relation->rd_partkeycxt = partkeycxt;
- oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);- relation->rd_partkey = copy_partition_key(key);+ relation->rd_partkey = key;
MemoryContextSwitchTo(oldcxt);
}
/*
- * copy_partition_key- *- * The copy is allocated in the current memory context.- */-static PartitionKey-copy_partition_key(PartitionKey fromkey)-{- PartitionKey newkey;- int n;-- newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));-- newkey->strategy = fromkey->strategy;- newkey->partnatts = n = fromkey->partnatts;-- newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));- memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));-- newkey->partexprs = copyObject(fromkey->partexprs);-- newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));- memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));-- newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));- memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));-- newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));- memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));-- newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));- memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));-- newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));- memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));-- newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));- memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));-- newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));- memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));-- newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));- memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));-- newkey->parttypalign = (char *) palloc(n * sizeof(bool));- memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));-- newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));- memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));-- return newkey;-}--/*
* equalRuleLocks
*
* Determine whether two RuleLocks are equivalent
--
2.11.0
From a1f384e1b234b020705a01f47a93bca3ede14210 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 23 Oct 2017 10:18:38 +0200
Subject: [PATCH v4 3/5] export generateClonedIndexStmt
---
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/equalfuncs.c | 1 +
src/backend/nodes/outfuncs.c | 1 +
src/backend/parser/gram.y | 1 +
src/backend/parser/parse_utilcmd.c | 19 +++++++++++--------
src/include/nodes/parsenodes.h | 4 ++++
src/include/parser/parse_utilcmd.h | 3 +++
7 files changed, 22 insertions(+), 8 deletions(-)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.cindex cadd253ef1..61f14a0f5a 100644--- a/src/backend/nodes/copyfuncs.c+++ b/src/backend/nodes/copyfuncs.c@@ -3365,6 +3365,7 @@ _copyIndexStmt(const IndexStmt *from)
COPY_STRING_FIELD(idxname);
COPY_NODE_FIELD(relation);
+ COPY_SCALAR_FIELD(relationId);
COPY_STRING_FIELD(accessMethod);
COPY_STRING_FIELD(tableSpace);
COPY_NODE_FIELD(indexParams);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.cindex 2866fd7b4a..788ce9c756 100644--- a/src/backend/nodes/equalfuncs.c+++ b/src/backend/nodes/equalfuncs.c@@ -1324,6 +1324,7 @@ _equalIndexStmt(const IndexStmt *a, const IndexStmt *b)
{
COMPARE_STRING_FIELD(idxname);
COMPARE_NODE_FIELD(relation);
+ COMPARE_SCALAR_FIELD(relationId);
COMPARE_STRING_FIELD(accessMethod);
COMPARE_STRING_FIELD(tableSpace);
COMPARE_NODE_FIELD(indexParams);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.cindex 291d1eeb46..b428825894 100644--- a/src/backend/nodes/outfuncs.c+++ b/src/backend/nodes/outfuncs.c@@ -2645,6 +2645,7 @@ _outIndexStmt(StringInfo str, const IndexStmt *node)
WRITE_STRING_FIELD(idxname);
WRITE_NODE_FIELD(relation);
+ WRITE_OID_FIELD(relationId);
WRITE_STRING_FIELD(accessMethod);
WRITE_STRING_FIELD(tableSpace);
WRITE_NODE_FIELD(indexParams);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.yindex c301ca465d..78cbf8029c 100644--- a/src/backend/parser/gram.y+++ b/src/backend/parser/gram.y@@ -7236,6 +7236,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
n->concurrent = $4;
n->idxname = $5;
n->relation = $7;
+ n->relationId = InvalidOid;
n->accessMethod = $8;
n->indexParams = $10;
n->options = $12;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.cindex 8461da490a..f9e8f6da0d 100644--- a/src/backend/parser/parse_utilcmd.c+++ b/src/backend/parser/parse_utilcmd.c@@ -117,9 +117,6 @@ static void transformTableLikeClause(CreateStmtContext *cxt,
TableLikeClause *table_like_clause);
static void transformOfType(CreateStmtContext *cxt,
TypeName *ofTypename);
-static IndexStmt *generateClonedIndexStmt(CreateStmtContext *cxt,- Relation source_idx,- const AttrNumber *attmap, int attmap_length);
static List *get_collation(Oid collation, Oid actual_datatype);
static List *get_opclass(Oid opclass, Oid actual_datatype);
static void transformIndexConstraints(CreateStmtContext *cxt);
@@ -1173,7 +1170,8 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
parent_index = index_open(parent_index_oid, AccessShareLock);
/* Build CREATE INDEX statement to recreate the parent_index */
- index_stmt = generateClonedIndexStmt(cxt, parent_index,+ index_stmt = generateClonedIndexStmt(cxt->relation, InvalidOid,+ parent_index,
attmap, tupleDesc->natts);
/* Copy comment on index, if requested */
@@ -1251,10 +1249,12 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
/*
* Generate an IndexStmt node using information from an already existing index
- * "source_idx". Attribute numbers should be adjusted according to attmap.+ * "source_idx", for the rel identified either by heapRel or heapRelid.+ *+ * Attribute numbers should be adjusted according to attmap.
*/
-static IndexStmt *-generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,+IndexStmt *+generateClonedIndexStmt(RangeVar *heapRel, Oid heapRelid, Relation source_idx,
const AttrNumber *attmap, int attmap_length)
{
Oid source_relid = RelationGetRelid(source_idx);
@@ -1275,6 +1275,8 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
Datum datum;
bool isnull;
+ Assert((heapRel != NULL) ^ OidIsValid(heapRelid));+
/*
* Fetch pg_class tuple of source index. We can't use the copy in the
* relcache entry because it doesn't include optional fields.
@@ -1310,7 +1312,8 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
/* Begin building the IndexStmt */
index = makeNode(IndexStmt);
- index->relation = cxt->relation;+ index->relation = heapRel;+ index->relationId = heapRelid;
index->accessMethod = pstrdup(NameStr(amrec->amname));
if (OidIsValid(idxrelrec->reltablespace))
index->tableSpace = get_tablespace_name(idxrelrec->reltablespace);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.hindex 34d6afc80f..b4ce3548dc 100644--- a/src/include/nodes/parsenodes.h+++ b/src/include/nodes/parsenodes.h@@ -2698,6 +2698,9 @@ typedef struct FetchStmt
* index, just a UNIQUE/PKEY constraint using an existing index. isconstraint
* must always be true in this case, and the fields describing the index
* properties are empty.
+ *+ * The relation to build the index on can be represented either by name+ * or by OID.
* ----------------------
*/
typedef struct IndexStmt
@@ -2705,6 +2708,7 @@ typedef struct IndexStmt
NodeTag type;
char *idxname; /* name of new index, or NULL for default */
RangeVar *relation; /* relation to build index on */
+ Oid relationId; /* OID of relation to build index on */
char *accessMethod; /* name of access method (eg. btree) */
char *tableSpace; /* tablespace, or NULL for default */
List *indexParams; /* columns to index: a list of IndexElem */
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.hindex e749432ef0..599f0e8e29 100644--- a/src/include/parser/parse_utilcmd.h+++ b/src/include/parser/parse_utilcmd.h@@ -27,5 +27,8 @@ extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt);
extern PartitionBoundSpec *transformPartitionBound(ParseState *pstate, Relation parent,
PartitionBoundSpec *spec);
+extern IndexStmt *generateClonedIndexStmt(RangeVar *heapRel, Oid heapOid,+ Relation source_idx,+ const AttrNumber *attmap, int attmap_length);
#endif /* PARSE_UTILCMD_H */
--
2.11.0
From 9b99656bbc5ac073871c491443f7577625cffcb3 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri, 6 Oct 2017 18:05:50 +0200
Subject: [PATCH v4 4/5] Allow indexes on partitioned tables
---
doc/src/sgml/ddl.sgml | 9 +-
doc/src/sgml/ref/alter_index.sgml | 28 ++-
doc/src/sgml/ref/create_index.sgml | 27 ++-
src/backend/access/common/reloptions.c | 1 +
src/backend/access/heap/heapam.c | 9 +-
src/backend/access/index/indexam.c | 3 +-
src/backend/bootstrap/bootparse.y | 2 +
src/backend/catalog/aclchk.c | 9 +-
src/backend/catalog/dependency.c | 3 +-
src/backend/catalog/heap.c | 1 +
src/backend/catalog/index.c | 101 ++++++++-
src/backend/catalog/objectaddress.c | 5 +-
src/backend/catalog/pg_depend.c | 13 +-
src/backend/catalog/toasting.c | 1 +
src/backend/commands/indexcmds.c | 264 ++++++++++++++++++++--
src/backend/commands/tablecmds.c | 353 +++++++++++++++++++++++++++++-
src/backend/parser/gram.y | 44 +++-
src/backend/parser/parse_utilcmd.c | 45 +++-
src/backend/tcop/utility.c | 9 +
src/backend/utils/adt/amutils.c | 3 +-
src/backend/utils/adt/ruleutils.c | 4 +-
src/backend/utils/cache/relcache.c | 30 ++-
src/bin/pg_dump/common.c | 70 ++++++
src/bin/pg_dump/pg_dump.c | 57 ++++-
src/bin/pg_dump/pg_dump.h | 4 +
src/bin/psql/describe.c | 20 +-
src/include/catalog/catversion.h | 2 +-
src/include/catalog/index.h | 6 +
src/include/catalog/pg_class.h | 1 +
src/include/catalog/pg_index.h | 38 ++--
src/include/commands/defrem.h | 3 +-
src/include/nodes/parsenodes.h | 5 +-
src/test/regress/expected/alter_table.out | 4 +-
src/test/regress/expected/indexing.out | 330 ++++++++++++++++++++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/serial_schedule | 1 +
src/test/regress/sql/indexing.sql | 142 ++++++++++++
37 files changed, 1538 insertions(+), 111 deletions(-)
create mode 100644 src/test/regress/expected/indexing.out
create mode 100644 src/test/regress/sql/indexing.sql
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgmlindex daba66c187..9282dfa352 100644--- a/doc/src/sgml/ddl.sgml+++ b/doc/src/sgml/ddl.sgml@@ -2922,7 +2922,9 @@ VALUES ('Albany', NULL, NULL, 'NY');
Partitions may themselves be defined as partitioned tables, using what is
called <firstterm>sub-partitioning</firstterm>. Partitions may have their
own indexes, constraints and default values, distinct from those of other
- partitions. Indexes must be created separately for each partition. See+ partitions. Indexes may be created separately for each partition, but+ any index created on the partitioned table will be inherited by its+ partitions. See
<xref linkend="sql-createtable"> for more details on creating partitioned
tables and partitions.
</para>
@@ -3268,9 +3270,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02
<itemizedlist>
<listitem>
<para>
- There is no facility available to create the matching indexes on all- partitions automatically. Indexes must be added to each partition with- separate commands. This also means that there is no way to create a+ While index creation is supported, constraints are not.+ This means that there is no way to create a
primary key, unique constraint, or exclusion constraint spanning all
partitions; it is only possible to constrain each leaf partition
individually.
diff --git a/doc/src/sgml/ref/alter_index.sgml b/doc/src/sgml/ref/alter_index.sgmlindex 5d0b792e50..e701103987 100644--- a/doc/src/sgml/ref/alter_index.sgml+++ b/doc/src/sgml/ref/alter_index.sgml@@ -23,7 +23,9 @@ PostgreSQL documentation
<synopsis>
ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> RENAME TO <replaceable class="parameter">new_name</replaceable>
ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> SET TABLESPACE <replaceable class="parameter">tablespace_name</replaceable>
-ALTER INDEX <replaceable class="parameter">name</replaceable> DEPENDS ON EXTENSION <replaceable class="parameter">extension_name</replaceable>+ALTER INDEX <replaceable class="parameter">name</replaceable> ATTACH <replaceable class="parameter">index_name</replaceable>+ALTER INDEX <replaceable class="parameter">name</replaceable> DETACH <replaceable class="parameter">index_name</replaceable>+ALTER INDEX DEPENDS ON EXTENSION <replaceable class="parameter">extension_name</replaceable>
ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">storage_parameter</replaceable> = <replaceable class="parameter">value</replaceable> [, ... ] )
ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> RESET ( <replaceable class="parameter">storage_parameter</replaceable> [, ... ] )
ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> ALTER [ COLUMN ] <replaceable class="parameter">column_number</replaceable>
@@ -76,6 +78,30 @@ ALTER INDEX ALL IN TABLESPACE <replaceable class="parameter">name</replaceable>
</varlistentry>
<varlistentry>
+ <term><literal>ATTACH</literal></term>+ <listitem>+ <para>+ Causes the named index to become attached to the altered index.+ The named index must be on a partition of the table containing the+ index being altered, and have an equivalent definition. An attached+ index cannot be dropped by itself, and will automatically be dropped+ if its parent index is dropped.+ </para>+ </listitem>+ </varlistentry>++ <varlistentry>+ <term><literal>DETACH</literal></term>+ <listitem>+ <para>+ Causes the named index to become detached from the altered index,+ severing their link and turning them into separately-operable+ objects.+ </para>+ </listitem>+ </varlistentry>++ <varlistentry>
<term><literal>DEPENDS ON EXTENSION</literal></term>
<listitem>
<para>
diff --git a/doc/src/sgml/ref/create_index.sgml b/doc/src/sgml/ref/create_index.sgmlindex 92c0090dfd..6f923e61a2 100644--- a/doc/src/sgml/ref/create_index.sgml+++ b/doc/src/sgml/ref/create_index.sgml@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> ] ON <replaceable class="parameter">table_name</replaceable> [ USING <replaceable class="parameter">method</replaceable> ]+CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> ] ON [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ USING <replaceable class="parameter">method</replaceable> ]
( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] [, ...] )
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> = <replaceable class="parameter">value</replaceable> [, ... ] ) ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
@@ -152,6 +152,16 @@ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] <replaceable class=
</varlistentry>
<varlistentry>
+ <term><literal>ONLY</literal></term>+ <listitem>+ <para>+ Indicates not to recurse creating indexes on partitions, if the+ table is partitioned. The default is to recurse.+ </para>+ </listitem>+ </varlistentry>++ <varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
<para>
@@ -546,6 +556,21 @@ Indexes:
</para>
<para>
+ When <literal>CREATE INDEX</literal> is invoked on a partitioned+ table, the default behavior is to recurse to all partitions to+ ensure they all have a matching indexes. Each partition is first+ checked to determine whether equivalent index already exists,+ and if so, that index will become attached as a partition index to+ the index being created, which will be its parent index. If no+ matching index exists, a new index will be created and automatically+ attached. If <literal>ONLY</literal> is specified, no recursion+ is done. Note, however, that any partition that is created in the+ future using <command>CREATE TABLE .. PARTITION OF</command> will+ automatically contain the index regardless of whether this option+ was specified.+ </para>++ <para>
For index methods that support ordered scans (currently, only B-tree),
the optional clauses <literal>ASC</literal>, <literal>DESC</literal>, <literal>NULLS
FIRST</literal>, and/or <literal>NULLS LAST</literal> can be specified to modify
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.cindex 3d0ce9af6f..c9cea08ace 100644--- a/src/backend/access/common/reloptions.c+++ b/src/backend/access/common/reloptions.c@@ -983,6 +983,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
options = view_reloptions(datum, false);
break;
case RELKIND_INDEX:
+ case RELKIND_PARTITIONED_INDEX:
options = index_reloptions(amoptions, datum, false);
break;
case RELKIND_FOREIGN_TABLE:
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.cindex 3acef279f4..b304944913 100644--- a/src/backend/access/heap/heapam.c+++ b/src/backend/access/heap/heapam.c@@ -1293,7 +1293,8 @@ heap_open(Oid relationId, LOCKMODE lockmode)
r = relation_open(relationId, lockmode);
- if (r->rd_rel->relkind == RELKIND_INDEX)+ if (r->rd_rel->relkind == RELKIND_INDEX ||+ r->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an index",
@@ -1321,7 +1322,8 @@ heap_openrv(const RangeVar *relation, LOCKMODE lockmode)
r = relation_openrv(relation, lockmode);
- if (r->rd_rel->relkind == RELKIND_INDEX)+ if (r->rd_rel->relkind == RELKIND_INDEX ||+ r->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an index",
@@ -1353,7 +1355,8 @@ heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
if (r)
{
- if (r->rd_rel->relkind == RELKIND_INDEX)+ if (r->rd_rel->relkind == RELKIND_INDEX ||+ r->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an index",
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.cindex edf4172eb2..c60db1eef5 100644--- a/src/backend/access/index/indexam.c+++ b/src/backend/access/index/indexam.c@@ -154,7 +154,8 @@ index_open(Oid relationId, LOCKMODE lockmode)
r = relation_open(relationId, lockmode);
- if (r->rd_rel->relkind != RELKIND_INDEX)+ if (r->rd_rel->relkind != RELKIND_INDEX &&+ r->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not an index",
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.yindex 2e1fef0350..95835ac1e7 100644--- a/src/backend/bootstrap/bootparse.y+++ b/src/backend/bootstrap/bootparse.y@@ -321,6 +321,7 @@ Boot_DeclareIndexStmt:
DefineIndex(relationId,
stmt,
$4,
+ InvalidOid,
false,
false,
false,
@@ -365,6 +366,7 @@ Boot_DeclareUniqueIndexStmt:
DefineIndex(relationId,
stmt,
$5,
+ InvalidOid,
false,
false,
false,
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.cindex ccde66a7dd..89e95ace37 100644--- a/src/backend/catalog/aclchk.c+++ b/src/backend/catalog/aclchk.c@@ -1766,7 +1766,8 @@ ExecGrant_Relation(InternalGrant *istmt)
pg_class_tuple = (Form_pg_class) GETSTRUCT(tuple);
/* Not sensible to grant on an index */
- if (pg_class_tuple->relkind == RELKIND_INDEX)+ if (pg_class_tuple->relkind == RELKIND_INDEX ||+ pg_class_tuple->relkind == RELKIND_PARTITIONED_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an index",
@@ -5347,7 +5348,8 @@ recordExtObjInitPriv(Oid objoid, Oid classoid)
pg_class_tuple = (Form_pg_class) GETSTRUCT(tuple);
/* Indexes don't have permissions */
- if (pg_class_tuple->relkind == RELKIND_INDEX)+ if (pg_class_tuple->relkind == RELKIND_INDEX ||+ pg_class_tuple->relkind == RELKIND_PARTITIONED_INDEX)
return;
/* Composite types don't have permissions either */
@@ -5632,7 +5634,8 @@ removeExtObjInitPriv(Oid objoid, Oid classoid)
pg_class_tuple = (Form_pg_class) GETSTRUCT(tuple);
/* Indexes don't have permissions */
- if (pg_class_tuple->relkind == RELKIND_INDEX)+ if (pg_class_tuple->relkind == RELKIND_INDEX ||+ pg_class_tuple->relkind == RELKIND_PARTITIONED_INDEX)
return;
/* Composite types don't have permissions either */
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.cindex 033c4358ea..720c6dd0bd 100644--- a/src/backend/catalog/dependency.c+++ b/src/backend/catalog/dependency.c@@ -1109,7 +1109,8 @@ doDeletion(const ObjectAddress *object, int flags)
{
char relKind = get_rel_relkind(object->objectId);
- if (relKind == RELKIND_INDEX)+ if (relKind == RELKIND_INDEX ||+ relKind == RELKIND_PARTITIONED_INDEX)
{
bool concurrent = ((flags & PERFORM_DELETION_CONCURRENTLY) != 0);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.cindex 9e14880b99..bc9fba9dfa 100644--- a/src/backend/catalog/heap.c+++ b/src/backend/catalog/heap.c@@ -294,6 +294,7 @@ heap_create(const char *relname,
case RELKIND_COMPOSITE_TYPE:
case RELKIND_FOREIGN_TABLE:
case RELKIND_PARTITIONED_TABLE:
+ case RELKIND_PARTITIONED_INDEX:
create_storage = false;
/*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.cindex 17faeffada..4a92c4d9d2 100644--- a/src/backend/catalog/index.c+++ b/src/backend/catalog/index.c@@ -41,6 +41,7 @@
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_constraint_fn.h"
+#include "catalog/pg_depend.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_tablespace.h"
@@ -98,6 +99,7 @@ static void InitializeAttributeOids(Relation indexRelation,
int numatts, Oid indexoid);
static void AppendAttributeTuples(Relation indexRelation, int numatts);
static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
+ Oid parentIndexId,
IndexInfo *indexInfo,
Oid *collationOids,
Oid *classOids,
@@ -551,6 +553,7 @@ AppendAttributeTuples(Relation indexRelation, int numatts)
static void
UpdateIndexRelation(Oid indexoid,
Oid heapoid,
+ Oid parentIndexOid,
IndexInfo *indexInfo,
Oid *collationOids,
Oid *classOids,
@@ -624,6 +627,7 @@ UpdateIndexRelation(Oid indexoid,
values[Anum_pg_index_indexrelid - 1] = ObjectIdGetDatum(indexoid);
values[Anum_pg_index_indrelid - 1] = ObjectIdGetDatum(heapoid);
+ values[Anum_pg_index_indparentidx - 1 ] = ObjectIdGetDatum(parentIndexOid);
values[Anum_pg_index_indnatts - 1] = Int16GetDatum(indexInfo->ii_NumIndexAttrs);
values[Anum_pg_index_indisunique - 1] = BoolGetDatum(indexInfo->ii_Unique);
values[Anum_pg_index_indisprimary - 1] = BoolGetDatum(primary);
@@ -670,6 +674,8 @@ UpdateIndexRelation(Oid indexoid,
* indexRelationId: normally, pass InvalidOid to let this routine
* generate an OID for the index. During bootstrap this may be
* nonzero to specify a preselected OID.
+ * parentIndexRelid: if creating an index partition, the OID of the+ * parent index; otherwise InvalidOid.
* relFileNode: normally, pass InvalidOid to get new storage. May be
* nonzero to attach an existing valid build.
* indexInfo: same info executor uses to insert into the index
@@ -695,6 +701,8 @@ UpdateIndexRelation(Oid indexoid,
* INDEX_CREATE_IF_NOT_EXISTS:
* do not throw an error if a relation with the same name
* already exists.
+ * INDEX_CREATE_PARTITIONED:+ * create a partitioned index (table must be partitioned)
* constr_flags: flags passed to index_constraint_create
* (only if INDEX_CREATE_ADD_CONSTRAINT is set)
* allow_system_table_mods: allow table to be a system catalog
@@ -706,6 +714,7 @@ Oid
index_create(Relation heapRelation,
const char *indexRelationName,
Oid indexRelationId,
+ Oid parentIndexRelid,
Oid relFileNode,
IndexInfo *indexInfo,
List *indexColNames,
@@ -732,11 +741,16 @@ index_create(Relation heapRelation,
char relpersistence;
bool isprimary = flags & INDEX_CREATE_IS_PRIMARY;
bool concurrent = flags & INDEX_CREATE_CONCURRENT;
+ bool partitioned = flags & INDEX_CREATE_PARTITIONED;+ char relkind;
/* constraint flags can only be set when constraint is requested */
Assert((constr_flags == 0) ||
((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0));
+ /* partitioned indexes must never be "built" by themselves */+ Assert(!partitioned || (flags & INDEX_CREATE_SKIP_BUILD));+ relkind = partitioned ? RELKIND_PARTITIONED_INDEX : RELKIND_INDEX;
is_exclusion = (indexInfo->ii_ExclusionOps != NULL);
pg_class = heap_open(RelationRelationId, RowExclusiveLock);
@@ -864,7 +878,7 @@ index_create(Relation heapRelation,
indexRelationId,
relFileNode,
indexTupDesc,
- RELKIND_INDEX,+ relkind,
relpersistence,
shared_relation,
mapped_relation,
@@ -921,7 +935,8 @@ index_create(Relation heapRelation,
* (Or, could define a rule to maintain the predicate) --Nels, Feb '92
* ----------------
*/
- UpdateIndexRelation(indexRelationId, heapRelationId, indexInfo,+ UpdateIndexRelation(indexRelationId, heapRelationId, parentIndexRelid,+ indexInfo,
collationObjectId, classObjectId, coloptions,
isprimary, is_exclusion,
(constr_flags & INDEX_CONSTR_CREATE_DEFERRABLE) == 0,
@@ -1010,6 +1025,17 @@ index_create(Relation heapRelation,
}
}
+ /* Store dependency on parent index, if any */+ if (OidIsValid(parentIndexRelid))+ {+ referenced.classId = RelationRelationId;+ referenced.objectId = parentIndexRelid;+ referenced.objectSubId = 0;++ recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);+ }++
/* Store dependency on collations */
/* The default collation is pinned, so don't bother recording it */
for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
@@ -1551,9 +1577,10 @@ index_drop(Oid indexId, bool concurrent)
}
/*
- * Schedule physical removal of the files+ * Schedule physical removal of the files (if any)
*/
- RelationDropStorage(userIndexRelation);+ if (userIndexRelation->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)+ RelationDropStorage(userIndexRelation);
/*
* Close and flush the index's relcache entry, to ensure relcache doesn't
@@ -1696,6 +1723,45 @@ BuildIndexInfo(Relation index)
return ii;
}
+/*+ * Compare two IndexInfos, and return true if they are similar enough that an+ * index built with one can pass as an index built with the other. If an+ * attmap is given, the indexes are from tables where the columns might be in+ * different physical locations, so use the map to match the column by name.+ */+bool+CompareIndexInfo(IndexInfo *info1, IndexInfo *info2, AttrNumber *attmap)+{+ int i;++ if (info1->ii_NumIndexAttrs != info2->ii_NumIndexAttrs)+ return false;++ for (i = 0; i < info1->ii_NumIndexAttrs; i++)+ {+ /* XXX use attmap here */+ if (info1->ii_KeyAttrNumbers[i] != info2->ii_KeyAttrNumbers[i])+ return false;+ }++ /* Expression indexes are currently not considered equal. Someday ... */+ if (info1->ii_Expressions != NIL || info2->ii_Expressions != NIL)+ return false;++ /* Can this be relaxed? */+ if (!equal(info1->ii_Predicate, info2->ii_Predicate))+ return false;++ /* Probably this can be relaxed someday */+ if (info1->ii_ExclusionOps != NULL || info2->ii_ExclusionOps != NULL)+ return false;++ if (info1->ii_Unique != info2->ii_Unique)+ return false;++ return true;+}+
/* ----------------
* BuildSpeculativeIndexInfo
* Add extra state to IndexInfo record
@@ -1918,6 +1984,9 @@ index_update_stats(Relation rel,
elog(ERROR, "could not find tuple for relation %u", relid);
rd_rel = (Form_pg_class) GETSTRUCT(tuple);
+ /* Should this be a more comprehensive test? */+ Assert(rd_rel->relkind != RELKIND_PARTITIONED_INDEX);+
/* Apply required updates, if any, to copied tuple */
dirty = false;
@@ -3328,6 +3397,14 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
iRel = index_open(indexId, AccessExclusiveLock);
/*
+ * The case of reindexing partitioned tables and indexes is handled+ * differently by upper layers, so this case shouldn't arise.+ */+ if (iRel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)+ elog(ERROR, "unsupported relation kind for index \"%s\"",+ RelationGetRelationName(iRel));++ /*
* Don't allow reindex on temp tables of other backends ... their local
* buffer manager is not going to cope.
*/
@@ -3526,6 +3603,22 @@ reindex_relation(Oid relid, int flags, int options)
*/
rel = heap_open(relid, ShareLock);
+ /*+ * This may be useful when implemented someday; but that day is not today.+ * For now, avoid erroring out when called in a multi-table context+ * (REINDEX SCHEMA) and happen to come across a partitioned table. The+ * partitions may be reindexed on their own anyway.+ */+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)+ {+ ereport(WARNING,+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),+ errmsg("REINDEX of partitioned tables is not yet implemented, skipping \"%s\"",+ RelationGetRelationName(rel))));+ heap_close(rel, ShareLock);+ return false;+ }+
toast_relid = rel->rd_rel->reltoastrelid;
/*
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.cindex 8d55c76fc4..0f7c7318f4 100644--- a/src/backend/catalog/objectaddress.c+++ b/src/backend/catalog/objectaddress.c@@ -1216,7 +1216,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *object,
switch (objtype)
{
case OBJECT_INDEX:
- if (relation->rd_rel->relkind != RELKIND_INDEX)+ if (relation->rd_rel->relkind != RELKIND_INDEX &&+ relation->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not an index",
@@ -3476,6 +3477,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
relname);
break;
case RELKIND_INDEX:
+ case RELKIND_PARTITIONED_INDEX:
appendStringInfo(buffer, _("index %s"),
relname);
break;
@@ -3950,6 +3952,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
appendStringInfoString(buffer, "table");
break;
case RELKIND_INDEX:
+ case RELKIND_PARTITIONED_INDEX:
appendStringInfoString(buffer, "index");
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.cindex cf0086b9bd..53db972dec 100644--- a/src/backend/catalog/pg_depend.c+++ b/src/backend/catalog/pg_depend.c@@ -656,14 +656,19 @@ get_constraint_index(Oid constraintId)
/*
* We assume any internal dependency of an index on the constraint
- * must be what we are looking for. (The relkind test is just- * paranoia; there shouldn't be any such dependencies otherwise.)+ * must be what we are looking for.
*/
if (deprec->classid == RelationRelationId &&
deprec->objsubid == 0 &&
- deprec->deptype == DEPENDENCY_INTERNAL &&- get_rel_relkind(deprec->objid) == RELKIND_INDEX)+ deprec->deptype == DEPENDENCY_INTERNAL)
{
+ char relkind = get_rel_relkind(deprec->objid);++ /* This is pure paranoia; there shouldn't be any such */+ if (relkind != RELKIND_INDEX &&+ relkind != RELKIND_PARTITIONED_INDEX)+ break;+
indexId = deprec->objid;
break;
}
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.cindex 539ca79ad3..b1be2bee36 100644--- a/src/backend/catalog/toasting.c+++ b/src/backend/catalog/toasting.c@@ -328,6 +328,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
coloptions[1] = 0;
index_create(toast_rel, toast_idxname, toastIndexOid, InvalidOid,
+ InvalidOid,
indexInfo,
list_make2("chunk_id", "chunk_seq"),
BTREE_AM_OID,
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.cindex 963cfedcb4..c289f62b9d 100644--- a/src/backend/commands/indexcmds.c+++ b/src/backend/commands/indexcmds.c@@ -23,7 +23,9 @@
#include "catalog/catalog.h"
#include "catalog/index.h"
#include "catalog/indexing.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_tablespace.h"
@@ -35,6 +37,7 @@
#include "commands/tablespace.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/planner.h"
@@ -77,6 +80,7 @@ static char *ChooseIndexNameAddition(List *colnames);
static List *ChooseIndexColumnNames(List *indexElems);
static void RangeVarCallbackForReindexIndex(const RangeVar *relation,
Oid relId, Oid oldRelId, void *arg);
+static void ReindexPartitionedIndex(Relation parentIdx);
/*
* CheckIndexCompatible
@@ -292,14 +296,15 @@ CheckIndexCompatible(Oid oldId,
* 'stmt': IndexStmt describing the properties of the new index.
* 'indexRelationId': normally InvalidOid, but during bootstrap can be
* nonzero to specify a preselected OID for the index.
+ * 'parentIndexId': the OID of the parent index; InvalidOid if not the child+ * of a partitioned index.
* 'is_alter_table': this is due to an ALTER rather than a CREATE operation.
* 'check_rights': check for CREATE rights in namespace and tablespace. (This
* should be true except when ALTER is deleting/recreating an index.)
* 'check_not_in_use': check for table not already in use in current session.
* This should be true unless caller is holding the table open, in which
* case the caller had better have checked it earlier.
- * 'skip_build': make the catalog entries but leave the index file empty;- * it will be filled later.+ * 'skip_build': make the catalog entries but don't create the index files
* 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
*
* Returns the object address of the created index.
@@ -308,6 +313,7 @@ ObjectAddress
DefineIndex(Oid relationId,
IndexStmt *stmt,
Oid indexRelationId,
+ Oid parentIndexId,
bool is_alter_table,
bool check_rights,
bool check_not_in_use,
@@ -330,6 +336,7 @@ DefineIndex(Oid relationId,
IndexAmRoutine *amRoutine;
bool amcanorder;
amoptions_function amoptions;
+ bool partitioned;
Datum reloptions;
int16 *coloptions;
IndexInfo *indexInfo;
@@ -382,23 +389,56 @@ DefineIndex(Oid relationId,
{
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_FOREIGN_TABLE:
+ /*+ * Custom error message for FOREIGN TABLE since the term is close+ * to a regular table and can confuse the user.+ */
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot create index on foreign table \"%s\"",
RelationGetRelationName(rel))));
- case RELKIND_PARTITIONED_TABLE:- ereport(ERROR,- (errcode(ERRCODE_WRONG_OBJECT_TYPE),- errmsg("cannot create index on partitioned table \"%s\"",- RelationGetRelationName(rel))));
default:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view",
RelationGetRelationName(rel))));
+ break;+ }++ /*+ * Establish behavior for partitioned tables, and verify sanity of+ * parameters.+ *+ * We do not build an actual index in this case; we only create a few+ * catalog entries. The actual indexes are built by recursing for each+ * partition.+ */+ partitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;+ if (partitioned)+ {+ if (stmt->concurrent)+ ereport(ERROR,+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),+ errmsg("cannot create index on partitioned table \"%s\" concurrently",+ RelationGetRelationName(rel))));+ if (stmt->unique)+ ereport(ERROR,+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),+ errmsg("cannot create unique index on partitioned table \"%s\"",+ RelationGetRelationName(rel))));+ if (stmt->excludeOpNames)+ ereport(ERROR,+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),+ errmsg("cannot create exclusion constraints on partitioned table \"%s\"",+ RelationGetRelationName(rel))));+ if (is_alter_table)+ ereport(ERROR,+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),+ errmsg("cannot create constraints on partitioned tables")));
}
/*
@@ -665,17 +705,20 @@ DefineIndex(Oid relationId,
/*
* Make the catalog entries for the index, including constraints. This
* step also actually builds the index, except if caller requested not to
- * or in concurrent mode, in which case it'll be done later.+ * or in concurrent mode, in which case it'll be done later, or+ * doing a partitioned index (because those don't have storage).
*/
flags = constr_flags = 0;
if (stmt->isconstraint)
flags |= INDEX_CREATE_ADD_CONSTRAINT;
- if (skip_build || stmt->concurrent)+ if (skip_build || stmt->concurrent || partitioned)
flags |= INDEX_CREATE_SKIP_BUILD;
if (stmt->if_not_exists)
flags |= INDEX_CREATE_IF_NOT_EXISTS;
if (stmt->concurrent)
flags |= INDEX_CREATE_CONCURRENT;
+ if (partitioned)+ flags |= INDEX_CREATE_PARTITIONED;
if (stmt->primary)
flags |= INDEX_CREATE_IS_PRIMARY;
@@ -685,8 +728,8 @@ DefineIndex(Oid relationId,
constr_flags |= INDEX_CONSTR_CREATE_INIT_DEFERRED;
indexRelationId =
- index_create(rel, indexRelationName, indexRelationId, stmt->oldNode,- indexInfo, indexColNames,+ index_create(rel, indexRelationName, indexRelationId, parentIndexId,+ stmt->oldNode, indexInfo, indexColNames,
accessMethodId, tablespaceId,
collationObjectId, classObjectId,
coloptions, reloptions,
@@ -706,6 +749,145 @@ DefineIndex(Oid relationId,
CreateComments(indexRelationId, RelationRelationId, 0,
stmt->idxcomment);
+ if (partitioned)+ {+ /*+ * If ONLY was specified, we're done. Otherwise, recurse to each+ * partition to create the corresponding index, or flag an existing+ * index as a children of this one.+ *+ * If we're invoked internally, recurse always.+ */+ if ((stmt->relation && stmt->relation->inh) || !stmt->relation)+ {+ PartitionDesc partdesc = RelationGetPartitionDesc(rel);+ Oid *part_oids;+ int nparts;+ MemoryContext oldcxt;+ TupleDesc parentDesc;++ /*+ * XXX In this block, we're going to call DefineIndex recursively+ * on the partitions; when building CONCURRENTLY, this will close+ * and open one transaction each time. If indexes exist on some+ * partitions, we will not use separate transactions for those.+ * That seems okay, since the changes done in that case will be+ * catalog only and should not take long.+ *+ * XXX Note that CONCURRENTLY is not implemented yet for+ * partitioned tables, though; make sure to fix any holes there+ * before enabling that ...+ */++ /*+ * For the concurrent case, we must ensure not to lose the array+ * of partitions we're going to work on, so copy it out of relcache.+ * PortalContext has the lifetime we need. Also, save a copy of+ * the relation's tuple desc.+ */+ oldcxt = MemoryContextSwitchTo(PortalContext);++ nparts = partdesc->nparts;+ part_oids = palloc(sizeof(Oid) * nparts);+ memcpy(part_oids, partdesc->oids, sizeof(Oid) * nparts);++ parentDesc = palloc(TupleDescSize(RelationGetDescr(rel)));+ TupleDescCopy(parentDesc, RelationGetDescr(rel));++ MemoryContextSwitchTo(oldcxt);++ heap_close(rel, NoLock);++ /*+ * Now recurse, one child at a time. Note that if any child is in+ * turn a partitioned table, this will recursively do the right thing.+ * In the concurrent case, this will close the current transaction and+ * open a new one.+ *+ * XXX Verify this coding carefully; see also ATExecAttachPartition+ */+ for (i = 0; i < nparts; i++)+ {+ Oid childRelid = part_oids[i];+ Relation childrel;+ List *childidxs;+ ListCell *cell;+ AttrNumber *attmap = NULL;+ bool found = false;++ childrel = heap_open(childRelid, lockmode);+ /* childidxs goes away with transaction in concurrent mode;+ * we leak it otherwise */+ childidxs = RelationGetIndexList(childrel);++ foreach(cell, childidxs)+ {+ Oid cldidxid = lfirst_oid(cell);+ Relation cldidx;+ IndexInfo *cldIdxInfo;++ cldidx = index_open(cldidxid, AccessShareLock);++ /* this index is already partition of another one */+ if (cldidx->rd_index->indparentidx != 0)+ {+ index_close(cldidx, AccessShareLock);+ continue;+ }++ cldIdxInfo = BuildIndexInfo(cldidx);+ if (attmap == NULL)+ attmap =+ convert_tuples_by_name_map(RelationGetDescr(childrel),+ parentDesc,+ gettext_noop("could not convert row type"));++ if (CompareIndexInfo(cldIdxInfo, indexInfo, attmap))+ {+ /* matched */+ indexSetParentIndex(cldidx, indexRelationId);+ found = true;+ index_close(cldidx, AccessShareLock);+ break;+ }++ index_close(cldidx, AccessShareLock);+ }++ heap_close(childrel, NoLock);++ if (!found)+ {+ IndexStmt *childStmt;++ childStmt = copyObject(stmt);+ childStmt->idxname = NULL;+ childStmt->relationId = childRelid;++ /*+ * Note that the event trigger command stash will only contain+ * a command to create the top-level index, not each of the+ * individual index partitions.+ */+ /* XXX emit a DDL event here? */+ DefineIndex(childRelid, childStmt,+ InvalidOid, /* no predefined OID */+ indexRelationId, /* this is our child */+ false, check_rights, check_not_in_use,+ false, quiet);+ }+ }+ }+ else+ heap_close(rel, NoLock);++ /*+ * Indexes on partitioned tables are not themselves built, so we're+ * done here.+ */+ return address;+ }+
if (!stmt->concurrent)
{
/* Close the heap and we're done, in the non-concurrent case */
@@ -1762,7 +1944,7 @@ ChooseIndexColumnNames(List *indexElems)
* ReindexIndex
* Recreate a specific index.
*/
-Oid+void
ReindexIndex(RangeVar *indexRelation, int options)
{
Oid indOid;
@@ -1785,12 +1967,17 @@ ReindexIndex(RangeVar *indexRelation, int options)
* lock on the index.
*/
irel = index_open(indOid, NoLock);
++ if (irel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)+ {+ ReindexPartitionedIndex(irel);+ return;+ }+
persistence = irel->rd_rel->relpersistence;
index_close(irel, NoLock);
reindex_index(indOid, false, persistence, options);
-- return indOid;
}
/*
@@ -1829,7 +2016,8 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
relkind = get_rel_relkind(relId);
if (!relkind)
return;
- if (relkind != RELKIND_INDEX)+ if (relkind != RELKIND_INDEX &&+ relkind != RELKIND_PARTITIONED_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not an index", relation->relname)));
@@ -1973,6 +2161,12 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
/*
* Only regular tables and matviews can have indexes, so ignore any
* other kind of relation.
+ *+ * XXX it is tempting to also consider partitioned tables here, but+ * that has the problem that if the children are in the same schema,+ * they would be processed twice. Maybe we could have a separate+ * list of partitioned tables, and expand that afterwards into relids,+ * ignoring any duplicates.
*/
if (classtuple->relkind != RELKIND_RELATION &&
classtuple->relkind != RELKIND_MATVIEW)
@@ -2035,3 +2229,43 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
MemoryContextDelete(private_context);
}
++/*+ * Reindex each child of a partitioned index.+ *+ * The parent index is given, locked in AccessExclusive mode; this routine+ * obtains the list of children and releases the lock on parent before+ * applying reindex on each child.+ */+static void+ReindexPartitionedIndex(Relation parentIdx)+{+ ereport(ERROR,+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),+ errmsg("REINDEX is not yet implemented for partitioned indexes")));+}++/*+ * Update the pg_index tuple corresponding to the given index on a partition+ * to indicate that the given index OID is now its parent partitioned index.+ */+void+indexSetParentIndex(Relation idx, Oid parentOid)+{+ Relation pgindex;+ HeapTuple indTup;+ Form_pg_index indForm;++ /* Make sure this is an index */+ Assert(idx->rd_rel->relkind == RELKIND_INDEX ||+ idx->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);++ pgindex = heap_open(IndexRelationId, RowExclusiveLock);+ indTup = idx->rd_indextuple;+ indForm = (Form_pg_index) GETSTRUCT(indTup);+ indForm->indparentidx = parentOid;++ CatalogTupleUpdate(pgindex, &(indTup->t_self), indTup);++ heap_close(pgindex, RowExclusiveLock);+}diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.cindex a723d98666..6fc71c9674 100644--- a/src/backend/commands/tablecmds.c+++ b/src/backend/commands/tablecmds.c@@ -266,6 +266,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
gettext_noop("table \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not a table"),
gettext_noop("Use DROP TABLE to remove a table.")},
+ {RELKIND_PARTITIONED_INDEX,+ ERRCODE_UNDEFINED_OBJECT,+ gettext_noop("index \"%s\" does not exist"),+ gettext_noop("index \"%s\" does not exist, skipping"),+ gettext_noop("\"%s\" is not an index"),+ gettext_noop("Use DROP INDEX to remove an index.")},
{'\0', 0, NULL, NULL, NULL, NULL}
};
@@ -481,6 +487,9 @@ static void ValidatePartitionConstraints(List **wqueue, Relation scanrel,
List *partConstraint,
bool validate_default);
static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
+static ObjectAddress ATExecAttachPartitionIdx(List **wqueue, Relation rel,+ RangeVar *name);+static ObjectAddress ATExecDetachPartitionIdx(Relation rel, RangeVar *name);
/* ----------------------------------------------------------------
@@ -901,6 +910,52 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
}
/*
+ * Now that the partition spec is installed, we can create any indexes+ * from the partitioned table into the partitions. We can't do it+ * earlier, because if this is a partition which in turn is partitioned,+ * DefineIndex would not be able to recurse correctly into children to+ * apply indexes from the parent table.+ */+ if (stmt->partbound)+ {+ Oid parentId = linitial_oid(inheritOids);+ Relation parent;+ List *idxlist;+ ListCell *cell;++ /* Already have strong enough lock on the parent */+ parent = heap_open(parentId, NoLock);+ idxlist = RelationGetIndexList(parent);++ /*+ * For each index in the parent table, create one in the partition+ */+ foreach(cell, idxlist)+ {+ Relation idxRel = index_open(lfirst_oid(cell), AccessShareLock);+ AttrNumber *attmap;+ IndexStmt *idxstmt;++ attmap = convert_tuples_by_name_map(RelationGetDescr(rel),+ RelationGetDescr(parent),+ gettext_noop("could not convert row type"));+ idxstmt =+ generateClonedIndexStmt(NULL, RelationGetRelid(rel), idxRel,+ attmap, RelationGetDescr(rel)->natts);+ DefineIndex(RelationGetRelid(rel),+ idxstmt,+ InvalidOid,+ RelationGetRelid(idxRel),+ false, false, false, false, false);++ index_close(idxRel, AccessShareLock);+ }++ list_free(idxlist);+ heap_close(parent, NoLock);+ }++ /*
* Now add any newly specified column default values and CHECK constraints
* to the new relation. These are passed to us in the form of raw
* parsetrees; we need to transform them to executable expression trees
@@ -1180,10 +1235,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
* but RemoveRelations() can only pass one relkind for a given relation.
* It chooses RELKIND_RELATION for both regular and partitioned tables.
* That means we must be careful before giving the wrong type error when
- * the relation is RELKIND_PARTITIONED_TABLE.+ * the relation is RELKIND_PARTITIONED_TABLE. There's an equivalent+ * problem with indexes.
*/
if (classform->relkind == RELKIND_PARTITIONED_TABLE)
expected_relkind = RELKIND_RELATION;
+ else if (classform->relkind == RELKIND_PARTITIONED_INDEX)+ expected_relkind = RELKIND_INDEX;
else
expected_relkind = classform->relkind;
@@ -1211,7 +1269,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
* we do it the other way around. No error if we don't find a pg_index
* entry, though --- the relation may have been dropped.
*/
- if (relkind == RELKIND_INDEX && relOid != oldRelOid)+ if ((relkind == RELKIND_INDEX || relkind == RELKIND_PARTITIONED_INDEX) &&+ relOid != oldRelOid)
{
state->heapOid = IndexGetRelation(relOid, true);
if (OidIsValid(state->heapOid))
@@ -2541,6 +2600,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
relkind != RELKIND_INDEX &&
+ relkind != RELKIND_PARTITIONED_INDEX &&
relkind != RELKIND_FOREIGN_TABLE &&
relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
@@ -3020,7 +3080,8 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal)
/*
* Also rename the associated constraint, if any.
*/
- if (targetrelation->rd_rel->relkind == RELKIND_INDEX)+ if (targetrelation->rd_rel->relkind == RELKIND_INDEX ||+ targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
{
Oid constraintId = get_index_constraint(myrelid);
@@ -3074,6 +3135,7 @@ CheckTableNotInUse(Relation rel, const char *stmt)
stmt, RelationGetRelationName(rel))));
if (rel->rd_rel->relkind != RELKIND_INDEX &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
AfterTriggerPendingOnRel(RelationGetRelid(rel)))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
@@ -3766,7 +3828,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_AttachPartition:
case AT_DetachPartition:
- ATSimplePermissions(rel, ATT_TABLE);+ ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX);
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
@@ -4113,10 +4175,18 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
ATExecGenericOptions(rel, (List *) cmd->def);
break;
case AT_AttachPartition:
- ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)+ ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);+ else+ ATExecAttachPartitionIdx(wqueue, rel,+ ((PartitionCmd *) cmd->def)->name);
break;
case AT_DetachPartition:
- ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)+ ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);+ else+ ATExecDetachPartitionIdx(rel,+ ((PartitionCmd *) cmd->def)->name);
break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
@@ -4751,6 +4821,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
actual_target = ATT_MATVIEW;
break;
case RELKIND_INDEX:
+ case RELKIND_PARTITIONED_INDEX:
actual_target = ATT_INDEX;
break;
case RELKIND_COMPOSITE_TYPE:
@@ -6195,6 +6266,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newVa
if (rel->rd_rel->relkind != RELKIND_RELATION &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
@@ -6206,7 +6278,9 @@ ATPrepSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newVa
* We allow referencing columns by numbers only for indexes, since table
* column numbers could contain gaps if columns are later dropped.
*/
- if (rel->rd_rel->relkind != RELKIND_INDEX && !colName)+ if (rel->rd_rel->relkind != RELKIND_INDEX &&+ rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&+ !colName)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot refer to non-index column by number")));
@@ -6284,7 +6358,8 @@ ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newVa
errmsg("cannot alter system column \"%s\"",
colName)));
- if (rel->rd_rel->relkind == RELKIND_INDEX &&+ if ((rel->rd_rel->relkind == RELKIND_INDEX ||+ rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) &&
rel->rd_index->indkey.values[attnum - 1] != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -6797,6 +6872,7 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
address = DefineIndex(RelationGetRelid(rel),
stmt,
InvalidOid, /* no predefined OID */
+ InvalidOid, /* no parent index */
true, /* is_alter_table */
check_rights,
false, /* check_not_in_use - we did it already */
@@ -9198,7 +9274,9 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
{
char relKind = get_rel_relkind(foundObject.objectId);
- if (relKind == RELKIND_INDEX)+ /* XXX does this do the right thing? Probably not */+ if (relKind == RELKIND_INDEX ||+ relKind == RELKIND_PARTITIONED_INDEX)
{
Assert(foundObject.objectSubId == 0);
if (!list_member_oid(tab->changedIndexOids, foundObject.objectId))
@@ -10162,6 +10240,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
*/
if (tuple_class->relkind != RELKIND_COMPOSITE_TYPE &&
tuple_class->relkind != RELKIND_INDEX &&
+ tuple_class->relkind != RELKIND_PARTITIONED_INDEX &&
tuple_class->relkind != RELKIND_TOASTVALUE)
changeDependencyOnOwner(RelationRelationId, relationOid,
newOwnerId);
@@ -10169,7 +10248,8 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
/*
* Also change the ownership of the table's row type, if it has one
*/
- if (tuple_class->relkind != RELKIND_INDEX)+ if (tuple_class->relkind != RELKIND_INDEX &&+ tuple_class->relkind != RELKIND_PARTITIONED_INDEX)
AlterTypeOwnerInternal(tuple_class->reltype, newOwnerId);
/*
@@ -10194,6 +10274,15 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
list_free(index_oid_list);
}
+#if 0+ /* For partitioned indexes, recurse to each child index */+ if (tuple_class->relkind == RELKIND_PARTITIONED_INDEX)+ {+ foreach (i, child_index_oid)+ ATExecChangeOwner(lfirst_oid(i), newOwnerId, true, lockmode);+ }+#endif+
if (tuple_class->relkind == RELKIND_RELATION ||
tuple_class->relkind == RELKIND_MATVIEW)
{
@@ -10486,6 +10575,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
(void) view_reloptions(newOptions, true);
break;
case RELKIND_INDEX:
+ case RELKIND_PARTITIONED_INDEX:
(void) index_reloptions(rel->rd_amroutine->amoptions, newOptions, true);
break;
default:
@@ -10898,7 +10988,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
relForm->relkind != RELKIND_RELATION &&
relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
(stmt->objtype == OBJECT_INDEX &&
- relForm->relkind != RELKIND_INDEX) ||+ relForm->relkind != RELKIND_INDEX &&+ relForm->relkind != RELKIND_PARTITIONED_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
relForm->relkind != RELKIND_MATVIEW))
continue;
@@ -13285,7 +13376,8 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a composite type", rv->relname)));
- if (reltype == OBJECT_INDEX && relkind != RELKIND_INDEX+ if (reltype == OBJECT_INDEX && relkind != RELKIND_INDEX &&+ relkind != RELKIND_PARTITIONED_INDEX
&& !IsA(stmt, RenameStmt))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -14006,6 +14098,103 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
StorePartitionBound(attachrel, rel, cmd->bound);
/*
+ * Ensure a correct set of indexes in the partition. This either creates+ * a new index in the table being attached, or re-parents an existing one.+ */+ {+ AttrNumber *attmap = NULL;+ List *idxes;+ List *attachRelIdxs;+ Relation *attachrelIdxRels;+ IndexInfo **attachInfos;+ int i;+ ListCell *cell;++ idxes = RelationGetIndexList(rel);+ attachRelIdxs = RelationGetIndexList(attachrel);+ attachrelIdxRels = palloc(sizeof(Relation) * list_length(attachRelIdxs));+ attachInfos = palloc(sizeof(IndexInfo *) * list_length(attachRelIdxs));++ i = 0;+ foreach(cell, attachRelIdxs)+ {+ Oid cldIdxId = lfirst_oid(cell);++ attachrelIdxRels[i] = index_open(cldIdxId, AccessShareLock);+ attachInfos[i] = BuildIndexInfo(attachrelIdxRels[i]);+ i++;+ }++ /*+ * For each index on the partitioned table, find a match in the table+ * being attached as partition; if one is not found, create one.+ */+ foreach(cell, idxes)+ {+ Oid idx = lfirst_oid(cell);+ Relation idxRel = index_open(idx, AccessShareLock);+ IndexInfo *info;+ bool found = false;++ if (idxRel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)+ {+ index_close(idxRel, AccessShareLock);+ continue;+ }+ info = BuildIndexInfo(idxRel);+ if (attmap == NULL)+ attmap =+ convert_tuples_by_name_map(RelationGetDescr(attachrel),+ RelationGetDescr(rel),+ gettext_noop("could not convert row type"));++ for (i = 0; i < list_length(attachRelIdxs); i++)+ {+ /* already used it */+ if (attachrelIdxRels[i]->rd_index->indparentidx != 0)+ continue;++ if (CompareIndexInfo(info, attachInfos[i], attmap))+ {+ /* bingo. */+ indexSetParentIndex(attachrelIdxRels[i], idx);+ found = true;+ break;+ }+ }+ if (!found)+ {+ IndexStmt *stmt;++ stmt = generateClonedIndexStmt(NULL, RelationGetRelid(attachrel),+ idxRel, attmap,+ RelationGetDescr(rel)->natts);+ /* XXX emit a DDL event here */+ DefineIndex(RelationGetRelid(attachrel), stmt, InvalidOid,+ RelationGetRelid(idxRel),+ false, false, false, false, false);+ }++ index_close(idxRel, AccessShareLock);+ }++ /* Clean up. */+ if (attmap)+ pfree(attmap);++ for (i = 0; i < list_length(attachRelIdxs); i++)+ {+ pfree(attachInfos[i]);+ index_close(attachrelIdxRels[i], AccessShareLock);+ }++ if (idxes)+ pfree(idxes);+ if (attachRelIdxs)+ pfree(attachRelIdxs);+ }++ /*
* Generate partition constraint from the partition bound specification.
* If the parent itself is a partition, make sure to include its
* constraint as well.
@@ -14163,3 +14352,143 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
return address;
}
++/*+ * ALTER INDEX i1 ATTACH PARTITION i2+ */+static ObjectAddress+ATExecAttachPartitionIdx(List **wqueue, Relation rel, RangeVar *name)+{+ Relation partRel;+ ObjectAddress address;++ /* XXX do we need this strong a lock? */+ partRel = relation_openrv(name, AccessExclusiveLock);++ /* Ensure it's what we need */+ if (partRel->rd_rel->relkind != RELKIND_INDEX &&+ partRel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)+ ereport(ERROR,+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),+ errmsg("\"%s\" is not an index", RelationGetRelationName(partRel))));++ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));++ /* Silently do nothing if already the right state */+ if (partRel->rd_index->indparentidx != RelationGetRelid(rel))+ {+ ObjectAddress parentAddr;+ Relation parentTable,+ partTable;+ IndexInfo *childInfo;+ IndexInfo *parentInfo;+ AttrNumber *attmap;+ bool found;+ int i;+ PartitionDesc partDesc;++ if (OidIsValid(partRel->rd_index->indparentidx))+ ereport(ERROR,+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),+ errmsg("index \"%s\" is already a partition of another index",+ RelationGetRelationName(partRel))));++ /* Make sure it indexes a partition of the other index's table */+ /* XXX need to avoid deadlocks here somehow */+ parentTable = heap_open(rel->rd_index->indrelid, AccessShareLock);+ partTable = heap_open(partRel->rd_index->indrelid, AccessShareLock);++ partDesc = RelationGetPartitionDesc(parentTable);+ found = false;+ for (i = 0; i < partDesc->nparts; i++)+ {+ if (partDesc->oids[i] == RelationGetRelid(partTable))+ {+ found = true;+ break;+ }+ }+ if (!found)+ ereport(ERROR,+ (errmsg("index \"%s\" is not on a partition of table \"%s\"",+ RelationGetRelationName(partRel),+ RelationGetRelationName(parentTable))));++ /*+ * Make sure the indexes are compatible. It seems like this could be+ * relaxed a bit: for instance maybe it'd be useful to have a hash index+ * on one partition and a btree index in another. But let's keep this+ * simple for now.+ */+ childInfo = BuildIndexInfo(partRel);+ parentInfo = BuildIndexInfo(rel);+ /* XXX are these inverted? */+ attmap = convert_tuples_by_name_map(RelationGetDescr(partTable),+ RelationGetDescr(parentTable),+ gettext_noop("could not convert row type"));+ if (!CompareIndexInfo(parentInfo, childInfo, attmap))+ ereport(ERROR,+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),+ errmsg("definition of index \"%s\" is not compatible with index \"%s\"",+ RelationGetRelationName(partRel),+ RelationGetRelationName(rel))));++ /* All good -- do it */+ indexSetParentIndex(partRel, RelationGetRelid(rel));++ /* set up pg_depend */+ ObjectAddressSet(parentAddr, RelationRelationId, RelationGetRelid(rel));+ recordDependencyOn(&address, &parentAddr, DEPENDENCY_INTERNAL);++ relation_close(parentTable, AccessShareLock);+ relation_close(partTable, AccessShareLock);+ }++ relation_close(partRel, AccessExclusiveLock);++ return address;+}++/*+ * ALTER INDEX i1 DETACH INDEX i2+ */+static ObjectAddress+ATExecDetachPartitionIdx(Relation rel, RangeVar *name)+{+ Relation partRel;+ ObjectAddress address;++ /* XXX do we need this strong a lock? */+ partRel = relation_openrv(name, AccessExclusiveLock);++ /* Ensure it's what we need */+ if (partRel->rd_rel->relkind != RELKIND_INDEX &&+ partRel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)+ ereport(ERROR,+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),+ errmsg("\"%s\" is not an index", RelationGetRelationName(partRel))));++ /* Silently do nothing if already the right state */+ if (OidIsValid(partRel->rd_index->indparentidx))+ {+ if (partRel->rd_index->indparentidx != RelationGetRelid(rel))+ ereport(ERROR,+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),+ errmsg("\"%s\" is not attached to \"%s\"",+ RelationGetRelationName(partRel),+ RelationGetRelationName(rel))));++ /* all good */+ indexSetParentIndex(partRel, InvalidOid);++ deleteDependencyRecordsForClass(RelationRelationId,+ RelationGetRelid(partRel),+ RelationRelationId,+ DEPENDENCY_INTERNAL);+ }++ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));+ relation_close(partRel, AccessExclusiveLock);++ return address;+}diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.yindex 78cbf8029c..2608396cae 100644--- a/src/backend/parser/gram.y+++ b/src/backend/parser/gram.y@@ -290,7 +290,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> add_drop opt_asc_desc opt_nulls_order
%type <node> alter_table_cmd alter_type_cmd opt_collate_clause
- replica_identity partition_cmd+ replica_identity partition_cmd index_partition_cmd
%type <list> alter_table_cmds alter_type_cmds
%type <list> alter_identity_column_option_list
%type <defelt> alter_identity_column_option
@@ -1876,6 +1876,15 @@ AlterTableStmt:
n->missing_ok = true;
$$ = (Node *)n;
}
+ | ALTER INDEX qualified_name index_partition_cmd+ {+ AlterTableStmt *n = makeNode(AlterTableStmt);+ n->relation = $3;+ n->cmds = list_make1($4);+ n->relkind = OBJECT_INDEX;+ n->missing_ok = false;+ $$ = (Node *)n;+ }
| ALTER INDEX ALL IN_P TABLESPACE name SET TABLESPACE name opt_nowait
{
AlterTableMoveAllStmt *n =
@@ -2010,6 +2019,35 @@ partition_cmd:
}
;
+index_partition_cmd:+ /* ALTER INDEX <name> ATTACH PARTITION <index_name> */+ ATTACH PARTITION qualified_name+ {+ AlterTableCmd *n = makeNode(AlterTableCmd);+ PartitionCmd *cmd = makeNode(PartitionCmd);++ n->subtype = AT_AttachPartition;+ cmd->name = $3;+ cmd->bound = NULL;+ n->def = (Node *) cmd;++ $$ = (Node *) n;+ }+ /* ALTER INDEX <name> DETACH PARTITION <index_name> */+ | DETACH PARTITION qualified_name+ {+ AlterTableCmd *n = makeNode(AlterTableCmd);+ PartitionCmd *cmd = makeNode(PartitionCmd);++ n->subtype = AT_DetachPartition;+ cmd->name = $3;+ cmd->bound = NULL;+ n->def = (Node *) cmd;++ $$ = (Node *) n;+ }+ ;+
alter_table_cmd:
/* ALTER TABLE <name> ADD <coldef> */
ADD_P columnDef
@@ -7228,7 +7266,7 @@ defacl_privilege_target:
*****************************************************************************/
IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
- ON qualified_name access_method_clause '(' index_params ')'+ ON relation_expr access_method_clause '(' index_params ')'
opt_reloptions OptTableSpace where_clause
{
IndexStmt *n = makeNode(IndexStmt);
@@ -7255,7 +7293,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
$$ = (Node *)n;
}
| CREATE opt_unique INDEX opt_concurrently IF_P NOT EXISTS index_name
- ON qualified_name access_method_clause '(' index_params ')'+ ON relation_expr access_method_clause '(' index_params ')'
opt_reloptions OptTableSpace where_clause
{
IndexStmt *n = makeNode(IndexStmt);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.cindex f9e8f6da0d..938e73f8ad 100644--- a/src/backend/parser/parse_utilcmd.c+++ b/src/backend/parser/parse_utilcmd.c@@ -3279,18 +3279,39 @@ transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd)
{
Relation parentRel = cxt->rel;
- /* the table must be partitioned */- if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)- ereport(ERROR,- (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),- errmsg("\"%s\" is not partitioned",- RelationGetRelationName(parentRel))));-- /* transform the partition bound, if any */- Assert(RelationGetPartitionKey(parentRel) != NULL);- if (cmd->bound != NULL)- cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,- cmd->bound);+ switch (parentRel->rd_rel->relkind)+ {+ case RELKIND_PARTITIONED_TABLE:+ /* transform the partition bound, if any */+ Assert(RelationGetPartitionKey(parentRel) != NULL);+ if (cmd->bound != NULL)+ cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,+ cmd->bound);+ break;+ case RELKIND_PARTITIONED_INDEX:+ /* nothing to check */+ Assert(cmd->bound == NULL);+ break;+ case RELKIND_RELATION:+ /* the table must be partitioned */+ ereport(ERROR,+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),+ errmsg("table \"%s\" is not partitioned",+ RelationGetRelationName(parentRel))));+ break;+ case RELKIND_INDEX:+ /* the index must be partitioned */+ ereport(ERROR,+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),+ errmsg("index \"%s\" is not partitioned",+ RelationGetRelationName(parentRel))));+ break;+ default:+ /* parser shouldn't let this case through */+ elog(ERROR, "\"%s\" is not a partitioned table or index",+ RelationGetRelationName(parentRel));+ break;+ }
}
/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.cindex 82a707af7b..93c09f73a8 100644--- a/src/backend/tcop/utility.c+++ b/src/backend/tcop/utility.c@@ -23,6 +23,7 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/namespace.h"
+#include "catalog/pg_inherits_fn.h"
#include "catalog/toasting.h"
#include "commands/alter.h"
#include "commands/async.h"
@@ -1296,6 +1297,7 @@ ProcessUtilitySlow(ParseState *pstate,
IndexStmt *stmt = (IndexStmt *) parsetree;
Oid relid;
LOCKMODE lockmode;
+ List *inheritors = NIL;
if (stmt->concurrent)
PreventTransactionChain(isTopLevel,
@@ -1317,6 +1319,9 @@ ProcessUtilitySlow(ParseState *pstate,
false, false,
RangeVarCallbackOwnsRelation,
NULL);
+ /* Also, lock any descendant tables if recursive */+ if (stmt->relation->inh)+ inheritors = find_all_inheritors(relid, lockmode, NULL);
/* Run parse analysis ... */
stmt = transformIndexStmt(relid, stmt, queryString);
@@ -1327,6 +1332,7 @@ ProcessUtilitySlow(ParseState *pstate,
DefineIndex(relid, /* OID of heap relation */
stmt,
InvalidOid, /* no predefined OID */
+ InvalidOid, /* no parent index */
false, /* is_alter_table */
true, /* check_rights */
true, /* check_not_in_use */
@@ -1342,6 +1348,9 @@ ProcessUtilitySlow(ParseState *pstate,
parsetree);
commandCollected = true;
EventTriggerAlterTableEnd();
++ if (inheritors)+ list_free(inheritors);
}
break;
diff --git a/src/backend/utils/adt/amutils.c b/src/backend/utils/adt/amutils.cindex f53b251b30..6af53402ce 100644--- a/src/backend/utils/adt/amutils.c+++ b/src/backend/utils/adt/amutils.c@@ -183,7 +183,8 @@ indexam_property(FunctionCallInfo fcinfo,
if (!HeapTupleIsValid(tuple))
PG_RETURN_NULL();
rd_rel = (Form_pg_class) GETSTRUCT(tuple);
- if (rd_rel->relkind != RELKIND_INDEX)+ if (rd_rel->relkind != RELKIND_INDEX &&+ rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
{
ReleaseSysCache(tuple);
PG_RETURN_NULL();
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.cindex 06cf32f5d7..0aaeb0f884 100644--- a/src/backend/utils/adt/ruleutils.c+++ b/src/backend/utils/adt/ruleutils.c@@ -1259,9 +1259,11 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
if (!attrsOnly)
{
if (!isConstraint)
- appendStringInfo(&buf, "CREATE %sINDEX %s ON %s USING %s (",+ appendStringInfo(&buf, "CREATE %sINDEX %s ON %s%s USING %s (",
idxrec->indisunique ? "UNIQUE " : "",
quote_identifier(NameStr(idxrelrec->relname)),
+ idxrelrec->relkind == RELKIND_PARTITIONED_INDEX ?+ "ONLY " : "",
generate_relation_name(indrelid, NIL),
quote_identifier(NameStr(amrec->amname)));
else /* currently, must be EXCLUDE constraint */
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.cindex 59280cd8bb..bddcbfc54f 100644--- a/src/backend/utils/cache/relcache.c+++ b/src/backend/utils/cache/relcache.c@@ -430,6 +430,7 @@ static void
RelationParseRelOptions(Relation relation, HeapTuple tuple)
{
bytea *options;
+ bool isindex;
relation->rd_options = NULL;
@@ -439,6 +440,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
+ case RELKIND_PARTITIONED_INDEX:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_PARTITIONED_TABLE:
@@ -452,10 +454,12 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
* we might not have any other for pg_class yet (consider executing this
* code for pg_class itself)
*/
+ isindex = relation->rd_rel->relkind == RELKIND_INDEX ||+ relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX;
options = extractRelOptions(tuple,
GetPgClassDescriptor(),
- relation->rd_rel->relkind == RELKIND_INDEX ?- relation->rd_amroutine->amoptions : NULL);+ isindex ? relation->rd_amroutine->amoptions :+ NULL);
/*
* Copy parsed data into CacheMemoryContext. To guard against the
@@ -2045,7 +2049,8 @@ RelationIdGetRelation(Oid relationId)
* and we don't want to use the full-blown procedure because it's
* a headache for indexes that reload itself depends on.
*/
- if (rd->rd_rel->relkind == RELKIND_INDEX)+ if (rd->rd_rel->relkind == RELKIND_INDEX ||+ rd->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
RelationReloadIndexInfo(rd);
else
RelationClearRelation(rd, true);
@@ -2159,7 +2164,8 @@ RelationReloadIndexInfo(Relation relation)
Form_pg_class relp;
/* Should be called only for invalidated indexes */
- Assert(relation->rd_rel->relkind == RELKIND_INDEX &&+ Assert((relation->rd_rel->relkind == RELKIND_INDEX ||+ relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) &&
!relation->rd_isvalid);
/* Ensure it's closed at smgr level */
@@ -2379,7 +2385,8 @@ RelationClearRelation(Relation relation, bool rebuild)
{
RelationInitPhysicalAddr(relation);
- if (relation->rd_rel->relkind == RELKIND_INDEX)+ if (relation->rd_rel->relkind == RELKIND_INDEX ||+ relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
{
relation->rd_isvalid = false; /* needs to be revalidated */
if (relation->rd_refcnt > 1 && IsTransactionState())
@@ -2395,7 +2402,8 @@ RelationClearRelation(Relation relation, bool rebuild)
* re-read the pg_class row to handle possible physical relocation of the
* index, and we check for pg_index updates too.
*/
- if (relation->rd_rel->relkind == RELKIND_INDEX &&+ if ((relation->rd_rel->relkind == RELKIND_INDEX ||+ relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) &&
relation->rd_refcnt > 0 &&
relation->rd_indexcxt != NULL)
{
@@ -5453,7 +5461,10 @@ load_relcache_init_file(bool shared)
rel->rd_att->constr = constr;
}
- /* If it's an index, there's more to do */+ /*+ * If it's an index, there's more to do. Note we explicitly ignore+ * partitioned indexes here.+ */
if (rel->rd_rel->relkind == RELKIND_INDEX)
{
MemoryContext indexcxt;
@@ -5815,7 +5826,10 @@ write_relcache_init_file(bool shared)
(rel->rd_options ? VARSIZE(rel->rd_options) : 0),
fp);
- /* If it's an index, there's more to do */+ /*+ * If it's an index, there's more to do. Note we explicitly ignore+ * partitioned indexes here.+ */
if (rel->rd_rel->relkind == RELKIND_INDEX)
{
/* write the pg_index tuple */
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.cindex 4b47951de1..7c379b8110 100644--- a/src/bin/pg_dump/common.c+++ b/src/bin/pg_dump/common.c@@ -68,6 +68,7 @@ static int numextmembers;
static void flagInhTables(Archive *fout, TableInfo *tbinfo, int numTables,
InhInfo *inhinfo, int numInherits);
+static void flagInhIndexes(Archive *fout, TableInfo *tblinfo, int numTables);
static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
static DumpableObject **buildIndexArray(void *objArray, int numObjs,
Size objSize);
@@ -76,6 +77,8 @@ static int ExtensionMemberIdCompare(const void *p1, const void *p2);
static void findParentsByOid(TableInfo *self,
InhInfo *inhinfo, int numInherits);
static int strInArray(const char *pattern, char **arr, int arr_size);
+static IndxInfo *findIndexByOid(Oid oid, DumpableObject **idxinfoindex,+ int numIndexes);
/*
@@ -258,6 +261,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
getIndexes(fout, tblinfo, numTables);
if (g_verbose)
+ write_msg(NULL, "flagging indexes in partitioned tables\n");+ flagInhIndexes(fout, tblinfo, numTables);++ if (g_verbose)
write_msg(NULL, "reading extended statistics\n");
getExtendedStatistics(fout, tblinfo, numTables);
@@ -354,6 +361,57 @@ flagInhTables(Archive *fout, TableInfo *tblinfo, int numTables,
}
}
+/*+ * flagInhIndexes -+ * Fill in each partitioned index's ->parentidx pointer.+ */+static void+flagInhIndexes(Archive *fout, TableInfo *tblinfo, int numTables)+{+ int i,+ j;+ DumpableObject ***parentIndexArray;++ parentIndexArray = (DumpableObject ***)+ pg_malloc0(getMaxDumpId() * sizeof(DumpableObject **));++ for (i = 0; i < numTables; i++)+ {+ TableInfo *parenttbl;++ if (!tblinfo[i].ispartition || tblinfo[i].numParents == 0)+ continue;++ Assert(tblinfo[i].numParents == 1);+ parenttbl = tblinfo[i].parents[0];++ if (parentIndexArray[parenttbl->dobj.dumpId] == NULL)+ parentIndexArray[parenttbl->dobj.dumpId] =+ buildIndexArray(parenttbl->indexes,+ parenttbl->numIndexes,+ sizeof(IndxInfo));++ for (j = 0; j < tblinfo[i].numIndexes; j++)+ {+ IndxInfo *index = &(tblinfo[i].indexes[j]);++ if (index->indparentidx == 0)+ continue;++ index->parentidx =+ findIndexByOid(index->indparentidx,+ parentIndexArray[parenttbl->dobj.dumpId],+ parenttbl->numIndexes);+ }+ }++ for (i = 0; i < numTables; i++)+ if (parentIndexArray[i])+ pg_free(parentIndexArray[i]);++ pg_free(parentIndexArray);+}+
/* flagInhAttrs -
* for each dumpable table in tblinfo, flag its inherited attributes
*
@@ -827,6 +885,18 @@ findExtensionByOid(Oid oid)
return (ExtensionInfo *) findObjectByOid(oid, extinfoindex, numExtensions);
}
+/*+ * findIndexByOid+ * find the entry of the index with the given oid+ *+ * This one's signature is different from the previous ones because we lack a+ * global array of all indexes, so caller must pass their array as argument.+ */+static IndxInfo *+findIndexByOid(Oid oid, DumpableObject **idxinfoindex, int numIndexes)+{+ return (IndxInfo *) findObjectByOid(oid, idxinfoindex, numIndexes);+}
/*
* setExtensionMembership
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.cindex d8fb356130..458fd5657d 100644--- a/src/bin/pg_dump/pg_dump.c+++ b/src/bin/pg_dump/pg_dump.c@@ -6509,6 +6509,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
int i_tableoid,
i_oid,
i_indexname,
+ i_parentidx,
i_indexdef,
i_indnkeys,
i_indkey,
@@ -6530,10 +6531,6 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
{
TableInfo *tbinfo = &tblinfo[i];
- /* Only plain tables and materialized views have indexes. */- if (tbinfo->relkind != RELKIND_RELATION &&- tbinfo->relkind != RELKIND_MATVIEW)- continue;
if (!tbinfo->hasindex)
continue;
@@ -6561,7 +6558,35 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
* is not.
*/
resetPQExpBuffer(query);
- if (fout->remoteVersion >= 90400)+ if (fout->remoteVersion >= 11000)+ {+ appendPQExpBuffer(query,+ "SELECT t.tableoid, t.oid, "+ "t.relname AS indexname, "+ "i.indparentidx, "+ "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "+ "t.relnatts AS indnkeys, "+ "i.indkey, i.indisclustered, "+ "i.indisreplident, t.relpages, "+ "c.contype, c.conname, "+ "c.condeferrable, c.condeferred, "+ "c.tableoid AS contableoid, "+ "c.oid AS conoid, "+ "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "+ "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "+ "t.reloptions AS indreloptions "+ "FROM pg_catalog.pg_index i "+ "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "+ "LEFT JOIN pg_catalog.pg_constraint c "+ "ON (i.indrelid = c.conrelid AND "+ "i.indexrelid = c.conindid AND "+ "c.contype IN ('p','u','x')) "+ "WHERE i.indrelid = '%u'::pg_catalog.oid "+ "AND i.indisvalid AND i.indisready "+ "ORDER BY indexname",+ tbinfo->dobj.catId.oid);+ }+ else if (fout->remoteVersion >= 90400)
{
/*
* the test on indisready is necessary in 9.2, and harmless in
@@ -6570,6 +6595,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
appendPQExpBuffer(query,
"SELECT t.tableoid, t.oid, "
"t.relname AS indexname, "
+ "0 AS indparentidx, "
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
"t.relnatts AS indnkeys, "
"i.indkey, i.indisclustered, "
@@ -6601,6 +6627,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
appendPQExpBuffer(query,
"SELECT t.tableoid, t.oid, "
"t.relname AS indexname, "
+ "0 AS indparentidx, "
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
"t.relnatts AS indnkeys, "
"i.indkey, i.indisclustered, "
@@ -6628,6 +6655,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
appendPQExpBuffer(query,
"SELECT t.tableoid, t.oid, "
"t.relname AS indexname, "
+ "0 AS indparentidx, "
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
"t.relnatts AS indnkeys, "
"i.indkey, i.indisclustered, "
@@ -6658,6 +6686,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
appendPQExpBuffer(query,
"SELECT t.tableoid, t.oid, "
"t.relname AS indexname, "
+ "0 AS indparentidx, "
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
"t.relnatts AS indnkeys, "
"i.indkey, i.indisclustered, "
@@ -6690,6 +6719,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_tableoid = PQfnumber(res, "tableoid");
i_oid = PQfnumber(res, "oid");
i_indexname = PQfnumber(res, "indexname");
+ i_parentidx = PQfnumber(res, "indparentidx");
i_indexdef = PQfnumber(res, "indexdef");
i_indnkeys = PQfnumber(res, "indnkeys");
i_indkey = PQfnumber(res, "indkey");
@@ -6706,8 +6736,10 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_tablespace = PQfnumber(res, "tablespace");
i_indreloptions = PQfnumber(res, "indreloptions");
- indxinfo = (IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo));+ tbinfo->indexes = indxinfo =+ (IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo));
constrinfo = (ConstraintInfo *) pg_malloc(ntups * sizeof(ConstraintInfo));
+ tbinfo->numIndexes = ntups;
for (j = 0; j < ntups; j++)
{
@@ -6729,6 +6761,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
indxinfo[j].indkeys, indxinfo[j].indnkeys);
indxinfo[j].indisclustered = (PQgetvalue(res, j, i_indisclustered)[0] == 't');
indxinfo[j].indisreplident = (PQgetvalue(res, j, i_indisreplident)[0] == 't');
+ indxinfo[j].indparentidx = atooid(PQgetvalue(res, j, i_parentidx));+ indxinfo[j].parentidx = NULL; /* set in flagInhIndexes */
indxinfo[j].relpages = atoi(PQgetvalue(res, j, i_relpages));
contype = *(PQgetvalue(res, j, i_contype));
@@ -16123,6 +16157,17 @@ dumpIndex(Archive *fout, IndxInfo *indxinfo)
fmtId(indxinfo->dobj.name));
}
+ /* If the index is a partition, attach it to its parent */+ if (indxinfo->parentidx)+ {+ appendPQExpBuffer(q, "\nALTER INDEX %s ",+ fmtQualifiedId(fout->remoteVersion,+ indxinfo->parentidx->dobj.namespace->dobj.name,+ indxinfo->parentidx->dobj.name));+ appendPQExpBuffer(q, "ATTACH PARTITION %s;\n",+ fmtId(indxinfo->dobj.name));+ }+
/*
* DROP must be fully qualified in case same name appears in
* pg_catalog
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.hindex da884ffd09..cdeda0a840 100644--- a/src/bin/pg_dump/pg_dump.h+++ b/src/bin/pg_dump/pg_dump.h@@ -328,6 +328,8 @@ typedef struct _tableInfo
*/
int numParents; /* number of (immediate) parent tables */
struct _tableInfo **parents; /* TableInfos of immediate parents */
+ int numIndexes; /* number of indexes */+ struct _indxInfo *indexes; /* indexes */
struct _tableDataInfo *dataObj; /* TableDataInfo, if dumping its data */
int numTriggers; /* number of triggers for table */
struct _triggerInfo *triggers; /* array of TriggerInfo structs */
@@ -361,6 +363,8 @@ typedef struct _indxInfo
Oid *indkeys;
bool indisclustered;
bool indisreplident;
+ Oid indparentidx; /* if partitioned, parent index OID */+ struct _indxInfo *parentidx; /* link to parent index (initially NULL) */
/* if there is an associated constraint object, its dumpId: */
DumpId indexconstraint;
int relpages; /* relpages of the underlying table */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.cindex b7b978a361..45502fe07c 100644--- a/src/bin/psql/describe.c+++ b/src/bin/psql/describe.c@@ -1703,7 +1703,8 @@ describeOneTableDetails(const char *schemaname,
appendPQExpBufferStr(&buf, ",\n a.attidentity");
else
appendPQExpBufferStr(&buf, ",\n ''::pg_catalog.char AS attidentity");
- if (tableinfo.relkind == RELKIND_INDEX)+ if (tableinfo.relkind == RELKIND_INDEX ||+ tableinfo.relkind == RELKIND_PARTITIONED_INDEX)
appendPQExpBufferStr(&buf, ",\n pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE) AS indexdef");
else
appendPQExpBufferStr(&buf, ",\n NULL AS indexdef");
@@ -1764,6 +1765,7 @@ describeOneTableDetails(const char *schemaname,
schemaname, relationname);
break;
case RELKIND_INDEX:
+ case RELKIND_PARTITIONED_INDEX:
if (tableinfo.relpersistence == 'u')
printfPQExpBuffer(&title, _("Unlogged index \"%s.%s\""),
schemaname, relationname);
@@ -1821,7 +1823,8 @@ describeOneTableDetails(const char *schemaname,
show_column_details = true;
}
- if (tableinfo.relkind == RELKIND_INDEX)+ if (tableinfo.relkind == RELKIND_INDEX ||+ tableinfo.relkind == RELKIND_PARTITIONED_INDEX)
headers[cols++] = gettext_noop("Definition");
if (tableinfo.relkind == RELKIND_FOREIGN_TABLE && pset.sversion >= 90200)
@@ -1832,6 +1835,7 @@ describeOneTableDetails(const char *schemaname,
headers[cols++] = gettext_noop("Storage");
if (tableinfo.relkind == RELKIND_RELATION ||
tableinfo.relkind == RELKIND_INDEX ||
+ tableinfo.relkind == RELKIND_PARTITIONED_INDEX ||
tableinfo.relkind == RELKIND_MATVIEW ||
tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
@@ -1904,7 +1908,8 @@ describeOneTableDetails(const char *schemaname,
}
/* Expression for index column */
- if (tableinfo.relkind == RELKIND_INDEX)+ if (tableinfo.relkind == RELKIND_INDEX ||+ tableinfo.relkind == RELKIND_PARTITIONED_INDEX)
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
/* FDW options for foreign table column, only for 9.2 or later */
@@ -1928,6 +1933,7 @@ describeOneTableDetails(const char *schemaname,
/* Statistics target, if the relkind supports this feature */
if (tableinfo.relkind == RELKIND_RELATION ||
tableinfo.relkind == RELKIND_INDEX ||
+ tableinfo.relkind == RELKIND_PARTITIONED_INDEX ||
tableinfo.relkind == RELKIND_MATVIEW ||
tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
@@ -2019,7 +2025,8 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- if (tableinfo.relkind == RELKIND_INDEX)+ if (tableinfo.relkind == RELKIND_INDEX ||+ tableinfo.relkind == RELKIND_PARTITIONED_INDEX)
{
/* Footer information about an index */
PGresult *result;
@@ -3372,6 +3379,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" WHEN 's' THEN '%s'"
" WHEN " CppAsString2(RELKIND_FOREIGN_TABLE) " THEN '%s'"
" WHEN " CppAsString2(RELKIND_PARTITIONED_TABLE) " THEN '%s'"
+ " WHEN " CppAsString2(RELKIND_PARTITIONED_INDEX) " THEN '%s'"
" END as \"%s\",\n"
" pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
gettext_noop("Schema"),
@@ -3384,6 +3392,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("special"),
gettext_noop("foreign table"),
gettext_noop("table"), /* partitioned table */
+ gettext_noop("index"), /* partitioned index */
gettext_noop("Type"),
gettext_noop("Owner"));
@@ -3429,7 +3438,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
if (showMatViews)
appendPQExpBufferStr(&buf, CppAsString2(RELKIND_MATVIEW) ",");
if (showIndexes)
- appendPQExpBufferStr(&buf, CppAsString2(RELKIND_INDEX) ",");+ appendPQExpBufferStr(&buf, CppAsString2(RELKIND_INDEX) ","+ CppAsString2(RELKIND_PARTITIONED_INDEX) ",");
if (showSeq)
appendPQExpBufferStr(&buf, CppAsString2(RELKIND_SEQUENCE) ",");
if (showSystem || pattern)
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.hindex bd4014a69d..b608dac156 100644--- a/src/include/catalog/catversion.h+++ b/src/include/catalog/catversion.h@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 201711092+#define CATALOG_VERSION_NO 201711101
#endif
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.hindex ab1f9ef1bd..8438ffe4ce 100644--- a/src/include/catalog/index.h+++ b/src/include/catalog/index.h@@ -47,10 +47,12 @@ extern void index_check_primary_key(Relation heapRel,
#define INDEX_CREATE_SKIP_BUILD (1 << 2)
#define INDEX_CREATE_CONCURRENT (1 << 3)
#define INDEX_CREATE_IF_NOT_EXISTS (1 << 4)
+#define INDEX_CREATE_PARTITIONED (1 << 5)
extern Oid index_create(Relation heapRelation,
const char *indexRelationName,
Oid indexRelationId,
+ Oid parentIndexRelid,
Oid relFileNode,
IndexInfo *indexInfo,
List *indexColNames,
@@ -84,6 +86,8 @@ extern void index_drop(Oid indexId, bool concurrent);
extern IndexInfo *BuildIndexInfo(Relation index);
+extern bool CompareIndexInfo(IndexInfo *info1, IndexInfo *info2, AttrNumber *attmap);+
extern void BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii);
extern void FormIndexDatum(IndexInfo *indexInfo,
@@ -134,4 +138,6 @@ extern bool ReindexIsProcessingHeap(Oid heapOid);
extern bool ReindexIsProcessingIndex(Oid indexOid);
extern Oid IndexGetRelation(Oid indexId, bool missing_ok);
+extern void indexSetParentIndex(Relation idx, Oid parentOid);+
#endif /* INDEX_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.hindex b256657bda..dd8e7ea2b5 100644--- a/src/include/catalog/pg_class.h+++ b/src/include/catalog/pg_class.h@@ -166,6 +166,7 @@ DESCR("");
#define RELKIND_COMPOSITE_TYPE 'c' /* composite type */
#define RELKIND_FOREIGN_TABLE 'f' /* foreign table */
#define RELKIND_PARTITIONED_TABLE 'p' /* partitioned table */
+#define RELKIND_PARTITIONED_INDEX 'I' /* partitioned index */
#define RELPERSISTENCE_PERMANENT 'p' /* regular table */
#define RELPERSISTENCE_UNLOGGED 'u' /* unlogged permanent table */
diff --git a/src/include/catalog/pg_index.h b/src/include/catalog/pg_index.hindex 8505c3be5f..e7afb0b921 100644--- a/src/include/catalog/pg_index.h+++ b/src/include/catalog/pg_index.h@@ -32,6 +32,7 @@ CATALOG(pg_index,2610) BKI_WITHOUT_OIDS BKI_SCHEMA_MACRO
{
Oid indexrelid; /* OID of the index */
Oid indrelid; /* OID of the relation it indexes */
+ Oid indparentidx; /* OID of parent index, 0 if not partitioned */
int16 indnatts; /* number of columns in index */
bool indisunique; /* is this a unique index? */
bool indisprimary; /* is this index for primary key? */
@@ -70,26 +71,27 @@ typedef FormData_pg_index *Form_pg_index;
* compiler constants for pg_index
* ----------------
*/
-#define Natts_pg_index 19+#define Natts_pg_index 20
#define Anum_pg_index_indexrelid 1
#define Anum_pg_index_indrelid 2
-#define Anum_pg_index_indnatts 3-#define Anum_pg_index_indisunique 4-#define Anum_pg_index_indisprimary 5-#define Anum_pg_index_indisexclusion 6-#define Anum_pg_index_indimmediate 7-#define Anum_pg_index_indisclustered 8-#define Anum_pg_index_indisvalid 9-#define Anum_pg_index_indcheckxmin 10-#define Anum_pg_index_indisready 11-#define Anum_pg_index_indislive 12-#define Anum_pg_index_indisreplident 13-#define Anum_pg_index_indkey 14-#define Anum_pg_index_indcollation 15-#define Anum_pg_index_indclass 16-#define Anum_pg_index_indoption 17-#define Anum_pg_index_indexprs 18-#define Anum_pg_index_indpred 19+#define Anum_pg_index_indparentidx 3+#define Anum_pg_index_indnatts 4+#define Anum_pg_index_indisunique 5+#define Anum_pg_index_indisprimary 6+#define Anum_pg_index_indisexclusion 7+#define Anum_pg_index_indimmediate 8+#define Anum_pg_index_indisclustered 9+#define Anum_pg_index_indisvalid 10+#define Anum_pg_index_indcheckxmin 11+#define Anum_pg_index_indisready 12+#define Anum_pg_index_indislive 13+#define Anum_pg_index_indisreplident 14+#define Anum_pg_index_indkey 15+#define Anum_pg_index_indcollation 16+#define Anum_pg_index_indclass 17+#define Anum_pg_index_indoption 18+#define Anum_pg_index_indexprs 19+#define Anum_pg_index_indpred 20
/*
* Index AMs that support ordered scans must support these two indoption
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.hindex bfead9af3d..fd2978f470 100644--- a/src/include/commands/defrem.h+++ b/src/include/commands/defrem.h@@ -25,12 +25,13 @@ extern void RemoveObjects(DropStmt *stmt);
extern ObjectAddress DefineIndex(Oid relationId,
IndexStmt *stmt,
Oid indexRelationId,
+ Oid parentIndexId,
bool is_alter_table,
bool check_rights,
bool check_not_in_use,
bool skip_build,
bool quiet);
-extern Oid ReindexIndex(RangeVar *indexRelation, int options);+extern void ReindexIndex(RangeVar *indexRelation, int options);
extern Oid ReindexTable(RangeVar *relation, int options);
extern void ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
int options);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.hindex b4ce3548dc..6751d7e0c0 100644--- a/src/include/nodes/parsenodes.h+++ b/src/include/nodes/parsenodes.h@@ -839,7 +839,7 @@ typedef struct PartitionRangeDatum
} PartitionRangeDatum;
/*
- * PartitionCmd - info for ALTER TABLE ATTACH/DETACH PARTITION commands+ * PartitionCmd - info for ALTER TABLE/INDEX ATTACH/DETACH PARTITION commands
*/
typedef struct PartitionCmd
{
@@ -2700,7 +2700,8 @@ typedef struct FetchStmt
* properties are empty.
*
* The relation to build the index on can be represented either by name
- * or by OID.+ * (in which case the RangeVar indicates whether to recurse or not) or by OID+ * (in which case the command is always recursive).
* ----------------------
*/
typedef struct IndexStmt
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.outindex 11f0baa11b..5a96b0b5e1 100644--- a/src/test/regress/expected/alter_table.out+++ b/src/test/regress/expected/alter_table.out@@ -3276,7 +3276,7 @@ CREATE TABLE unparted (
);
CREATE TABLE fail_part (like unparted);
ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
-ERROR: "unparted" is not partitioned+ERROR: table "unparted" is not partitioned
DROP TABLE unparted, fail_part;
-- check that partition bound is compatible
CREATE TABLE list_parted (
@@ -3656,7 +3656,7 @@ DROP TABLE fail_part;
-- check that the table is partitioned at all
CREATE TABLE regular_table (a int);
ALTER TABLE regular_table DETACH PARTITION any_name;
-ERROR: "regular_table" is not partitioned+ERROR: table "regular_table" is not partitioned
DROP TABLE regular_table;
-- check that the partition being detached exists at all
ALTER TABLE list_parted2 DETACH PARTITION part_4;
diff --git a/src/test/regress/expected/indexing.out b/src/test/regress/expected/indexing.outnew file mode 100644index 0000000000..cde29bebb5--- /dev/null+++ b/src/test/regress/expected/indexing.out@@ -0,0 +1,330 @@+-- Creating an index on a partitioned table makes the partitions+-- automatically get the index+create table idxpart (a int, b int, c text) partition by range (a);+create table idxpart1 partition of idxpart for values from (0) to (10);+create table idxpart2 partition of idxpart for values from (10) to (100)+ partition by range (b);+create table idxpart21 partition of idxpart2 for values from (0) to (100);+create index on idxpart (a);+select relname, relkind, indparentidx::regclass+ from pg_class left join pg_index on (indexrelid = oid)+ where relname like 'idxpart%' order by relname;+ relname | relkind | indparentidx +-----------------+---------+----------------+ idxpart | p | + idxpart1 | r | + idxpart1_a_idx | i | idxpart_a_idx+ idxpart2 | p | + idxpart21 | r | + idxpart21_a_idx | i | idxpart2_a_idx+ idxpart2_a_idx | I | idxpart_a_idx+ idxpart_a_idx | I | -+(8 rows)++drop table idxpart;+-- Some unsupported cases+create table idxpart (a int, b int, c text) partition by range (a);+create table idxpart1 partition of idxpart for values from (0) to (10);+create unique index on idxpart (a);+ERROR: cannot create unique index on partitioned table "idxpart"+create index concurrently on idxpart (a);+ERROR: cannot create index on partitioned table "idxpart" concurrently+drop table idxpart;+-- If a table without index is attached as partition to a table with+-- an index, the index is automatically created+create table idxpart (a int, b int, c text) partition by range (a);+create index idxparti on idxpart (a);+create index idxparti2 on idxpart (b, c);+create table idxpart1 (like idxpart);+\d idxpart1+ Table "public.idxpart1"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | + b | integer | | | + c | text | | | ++alter table idxpart attach partition idxpart1 for values from (0) to (10);+\d idxpart1+ Table "public.idxpart1"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | + b | integer | | | + c | text | | | +Partition of: idxpart FOR VALUES FROM (0) TO (10)+Indexes:+ "idxpart1_a_idx" btree (a)+ "idxpart1_b_c_idx" btree (b, c)++drop table idxpart;+-- If a partition already has an index, make sure we don't create a separate+-- one+create table idxpart (a int, b int) partition by range (a, b);+create table idxpart1 partition of idxpart for values from (0, 0) to (10, 10);+create index on idxpart1 (a, b);+create index on idxpart (a, b);+\d idxpart1+ Table "public.idxpart1"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | + b | integer | | | +Partition of: idxpart FOR VALUES FROM (0, 0) TO (10, 10)+Indexes:+ "idxpart1_a_b_idx" btree (a, b)++select indexrelid::regclass, indrelid::regclass, indparentidx::regclass+ from pg_index where indexrelid::regclass::text like 'idxpart%'+ order by indexrelid::regclass;+ indexrelid | indrelid | indparentidx +------------------+----------+-----------------+ idxpart1_a_b_idx | idxpart1 | idxpart_a_b_idx+ idxpart_a_b_idx | idxpart | -+(2 rows)++create index idxpart1_tst1 on idxpart1 (b, a);+create index idxpart1_tst2 on idxpart1 using hash (a);+create index idxpart1_tst3 on idxpart1 (a, b) where a > 10;+-- ALTER INDEX .. ATTACH/DETACH, error cases+alter index idxpart attach partition idxpart1;+ERROR: "idxpart" is not an index+alter index idxpart detach partition idxpart1;+ERROR: "idxpart" is not an index+alter index idxpart_a_b_idx attach partition idxpart1;+ERROR: "idxpart1" is not an index+alter index idxpart_a_b_idx detach partition idxpart1;+ERROR: "idxpart1" is not an index+alter index idxpart_a_b_idx attach partition idxpart_a_b_idx;+ERROR: index "idxpart_a_b_idx" is not on a partition of table "idxpart"+alter index idxpart_a_b_idx attach partition idxpart1_b_idx;+ERROR: relation "idxpart1_b_idx" does not exist+alter index idxpart_a_b_idx attach partition idxpart1_tst1;+ERROR: definition of index "idxpart1_tst1" is not compatible with index "idxpart_a_b_idx"+alter index idxpart_a_b_idx attach partition idxpart1_tst2;+ERROR: definition of index "idxpart1_tst2" is not compatible with index "idxpart_a_b_idx"+alter index idxpart_a_b_idx attach partition idxpart1_tst3;+ERROR: definition of index "idxpart1_tst3" is not compatible with index "idxpart_a_b_idx"+-- OK+alter index idxpart_a_b_idx attach partition idxpart1_a_b_idx;+alter index idxpart_a_b_idx attach partition idxpart1_a_b_idx; -- quiet+alter index idxpart_a_b_idx detach partition idxpart1_a_b_idx;+alter index idxpart_a_b_idx detach partition idxpart1_a_b_idx; -- quiet+drop table idxpart;+-- make sure everything's gone+select indexrelid::regclass, indrelid::regclass, indparentidx::regclass+ from pg_index where indexrelid::regclass::text like 'idxpart%';+ indexrelid | indrelid | indparentidx +------------+----------+--------------+(0 rows)++-- If CREATE INDEX ONLY, don't create indexes on partitions; and existing+-- indexes on partitions don't change parent. ALTER INDEX ATTACH can change+-- the parent after the fact.+create table idxpart (a int) partition by range (a);+create table idxpart1 partition of idxpart for values from (0) to (100);+create table idxpart2 partition of idxpart for values from (100) to (1000)+ partition by range (a);+create table idxpart21 partition of idxpart2 for values from (100) to (200);+create table idxpart22 partition of idxpart2 for values from (200) to (300);+create index on idxpart22 (a);+create index on only idxpart2 (a);+create index on idxpart (a);+-- Here we expect that idxpart1 and idxpart2 have a new index, but idxpart21+-- does not; also, idxpart22 is detached.+\d idxpart1+ Table "public.idxpart1"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | +Partition of: idxpart FOR VALUES FROM (0) TO (100)+Indexes:+ "idxpart1_a_idx" btree (a)++\d idxpart2+ Table "public.idxpart2"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | +Partition of: idxpart FOR VALUES FROM (100) TO (1000)+Partition key: RANGE (a)+Indexes:+ "idxpart2_a_idx" btree (a)+Number of partitions: 2 (Use \d+ to list them.)++\d idxpart21+ Table "public.idxpart21"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | +Partition of: idxpart2 FOR VALUES FROM (100) TO (200)++select indexrelid::regclass, indrelid::regclass, indparentidx::regclass+ from pg_index where indexrelid::regclass::text like 'idxpart%'+ order by indrelid::regclass;+ indexrelid | indrelid | indparentidx +-----------------+-----------+---------------+ idxpart_a_idx | idxpart | -+ idxpart1_a_idx | idxpart1 | idxpart_a_idx+ idxpart2_a_idx | idxpart2 | idxpart_a_idx+ idxpart22_a_idx | idxpart22 | -+(4 rows)++alter index idxpart2_a_idx attach partition idxpart22_a_idx;+select indexrelid::regclass, indrelid::regclass, indparentidx::regclass+ from pg_index where indexrelid::regclass::text like 'idxpart%'+ order by indrelid::regclass;+ indexrelid | indrelid | indparentidx +-----------------+-----------+----------------+ idxpart_a_idx | idxpart | -+ idxpart1_a_idx | idxpart1 | idxpart_a_idx+ idxpart2_a_idx | idxpart2 | idxpart_a_idx+ idxpart22_a_idx | idxpart22 | idxpart2_a_idx+(4 rows)++drop table idxpart;+-- When a table is attached a partition and it already has an index, a+-- duplicate index should not get created, but rather the index becomes+-- attached to the parent's index.+create table idxpart (a int, b int, c text) partition by range (a);+create index idxparti on idxpart (a);+create index idxparti2 on idxpart (b, c);+create table idxpart1 (like idxpart including indexes);+\d idxpart1+ Table "public.idxpart1"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | + b | integer | | | + c | text | | | +Indexes:+ "idxpart1_a_idx" btree (a)+ "idxpart1_b_c_idx" btree (b, c)++select relname, relkind, indparentidx::regclass+ from pg_class left join pg_index on (indexrelid = oid)+ where relname like 'idxpart%' order by relname;+ relname | relkind | indparentidx +------------------+---------+--------------+ idxpart | p | + idxpart1 | r | + idxpart1_a_idx | i | -+ idxpart1_b_c_idx | i | -+ idxparti | I | -+ idxparti2 | I | -+(6 rows)++alter table idxpart attach partition idxpart1 for values from (0) to (10);+\d idxpart1+ Table "public.idxpart1"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | + b | integer | | | + c | text | | | +Partition of: idxpart FOR VALUES FROM (0) TO (10)+Indexes:+ "idxpart1_a_idx" btree (a)+ "idxpart1_b_c_idx" btree (b, c)++select relname, relkind, indparentidx::regclass+ from pg_class left join pg_index on (indexrelid = oid)+ where relname like 'idxpart%' order by relname;+ relname | relkind | indparentidx +------------------+---------+--------------+ idxpart | p | + idxpart1 | r | + idxpart1_a_idx | i | idxparti+ idxpart1_b_c_idx | i | idxparti2+ idxparti | I | -+ idxparti2 | I | -+(6 rows)++drop table idxpart;+-- Make sure the partition columns are mapped correctly+create table idxpart (a int, b int, c text) partition by range (a);+create index idxparti on idxpart (a);+create index idxparti2 on idxpart (c, b);+create table idxpart1 (c text, a int, b int);+alter table idxpart attach partition idxpart1 for values from (0) to (10);+\d idxpart1+ Table "public.idxpart1"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ c | text | | | + a | integer | | | + b | integer | | | +Partition of: idxpart FOR VALUES FROM (0) TO (10)+Indexes:+ "idxpart1_a_idx" btree (a)+ "idxpart1_c_b_idx" btree (c, b)++drop table idxpart;+-- Make sure things work if either table has dropped columns+create table idxpart (a int, b int, c int, d int) partition by range (a);+alter table idxpart drop column c;+create index idxparti on idxpart (a);+create index idxparti2 on idxpart (b, d);+create table idxpart1 (like idxpart);+alter table idxpart attach partition idxpart1 for values from (0) to (10);+\d idxpart1+ Table "public.idxpart1"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | + b | integer | | | + d | integer | | | +Partition of: idxpart FOR VALUES FROM (0) TO (10)+Indexes:+ "idxpart1_a_idx" btree (a)+ "idxpart1_b_d_idx" btree (b, d)++select attrelid::regclass, attname, attnum from pg_attribute+ where attrelid in ('idxpart'::regclass, 'idxpart1'::regclass) and attnum > 0+ order by attrelid::regclass, attnum;+ attrelid | attname | attnum +----------+------------------------------+--------+ idxpart | a | 1+ idxpart | b | 2+ idxpart | ........pg.dropped.3........ | 3+ idxpart | d | 4+ idxpart1 | a | 1+ idxpart1 | b | 2+ idxpart1 | d | 3+(7 rows)++drop table idxpart;+create table idxpart (a int, b int, c int) partition by range (a);+create table idxpart1 (zz int, like idxpart, aa int);+alter table idxpart1 drop column zz, drop column aa;+create index idxparti on idxpart (a);+create index idxparti2 on idxpart (b, c);+alter table idxpart attach partition idxpart1 for values from (0) to (10);+\d idxpart1+ Table "public.idxpart1"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | + b | integer | | | + c | integer | | | +Partition of: idxpart FOR VALUES FROM (0) TO (10)+Indexes:+ "idxpart1_a_idx" btree (a)+ "idxpart1_b_c_idx" btree (b, c)++select attrelid::regclass, attname, attnum from pg_attribute+ where attrelid in ('idxpart'::regclass, 'idxpart1'::regclass) and attnum > 0+ order by attrelid::regclass, attnum;+ attrelid | attname | attnum +----------+------------------------------+--------+ idxpart | a | 1+ idxpart | b | 2+ idxpart | c | 3+ idxpart1 | ........pg.dropped.1........ | 1+ idxpart1 | a | 2+ idxpart1 | b | 3+ idxpart1 | c | 4+ idxpart1 | ........pg.dropped.5........ | 5+(8 rows)++drop table idxpart;diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_scheduleindex aa5e6af621..591e7da337 100644--- a/src/test/regress/parallel_schedule+++ b/src/test/regress/parallel_schedule@@ -116,7 +116,7 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid c
# ----------
# Another group of parallel tests
# ----------
-test: identity partition_join reloptions+test: identity partition_join reloptions indexing
# event triggers cannot run concurrently with any test that runs DDL
test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_scheduleindex 3866314a92..d5b5ec8472 100644--- a/src/test/regress/serial_schedule+++ b/src/test/regress/serial_schedule@@ -181,5 +181,6 @@ test: xml
test: identity
test: partition_join
test: reloptions
+test: indexing
test: event_trigger
test: stats
diff --git a/src/test/regress/sql/indexing.sql b/src/test/regress/sql/indexing.sqlnew file mode 100644index 0000000000..53a28f120f--- /dev/null+++ b/src/test/regress/sql/indexing.sql@@ -0,0 +1,142 @@+-- Creating an index on a partitioned table makes the partitions+-- automatically get the index+create table idxpart (a int, b int, c text) partition by range (a);+create table idxpart1 partition of idxpart for values from (0) to (10);+create table idxpart2 partition of idxpart for values from (10) to (100)+ partition by range (b);+create table idxpart21 partition of idxpart2 for values from (0) to (100);+create index on idxpart (a);+select relname, relkind, indparentidx::regclass+ from pg_class left join pg_index on (indexrelid = oid)+ where relname like 'idxpart%' order by relname;+drop table idxpart;++-- Some unsupported cases+create table idxpart (a int, b int, c text) partition by range (a);+create table idxpart1 partition of idxpart for values from (0) to (10);+create unique index on idxpart (a);+create index concurrently on idxpart (a);+drop table idxpart;++-- If a table without index is attached as partition to a table with+-- an index, the index is automatically created+create table idxpart (a int, b int, c text) partition by range (a);+create index idxparti on idxpart (a);+create index idxparti2 on idxpart (b, c);+create table idxpart1 (like idxpart);+\d idxpart1+alter table idxpart attach partition idxpart1 for values from (0) to (10);+\d idxpart1+drop table idxpart;++-- If a partition already has an index, make sure we don't create a separate+-- one+create table idxpart (a int, b int) partition by range (a, b);+create table idxpart1 partition of idxpart for values from (0, 0) to (10, 10);+create index on idxpart1 (a, b);+create index on idxpart (a, b);+\d idxpart1+select indexrelid::regclass, indrelid::regclass, indparentidx::regclass+ from pg_index where indexrelid::regclass::text like 'idxpart%'+ order by indexrelid::regclass;+create index idxpart1_tst1 on idxpart1 (b, a);+create index idxpart1_tst2 on idxpart1 using hash (a);+create index idxpart1_tst3 on idxpart1 (a, b) where a > 10;++-- ALTER INDEX .. ATTACH/DETACH, error cases+alter index idxpart attach partition idxpart1;+alter index idxpart detach partition idxpart1;+alter index idxpart_a_b_idx attach partition idxpart1;+alter index idxpart_a_b_idx detach partition idxpart1;+alter index idxpart_a_b_idx attach partition idxpart_a_b_idx;+alter index idxpart_a_b_idx attach partition idxpart1_b_idx;+alter index idxpart_a_b_idx attach partition idxpart1_tst1;+alter index idxpart_a_b_idx attach partition idxpart1_tst2;+alter index idxpart_a_b_idx attach partition idxpart1_tst3;+-- OK+alter index idxpart_a_b_idx attach partition idxpart1_a_b_idx;+alter index idxpart_a_b_idx attach partition idxpart1_a_b_idx; -- quiet+alter index idxpart_a_b_idx detach partition idxpart1_a_b_idx;+alter index idxpart_a_b_idx detach partition idxpart1_a_b_idx; -- quiet+drop table idxpart;+-- make sure everything's gone+select indexrelid::regclass, indrelid::regclass, indparentidx::regclass+ from pg_index where indexrelid::regclass::text like 'idxpart%';++-- If CREATE INDEX ONLY, don't create indexes on partitions; and existing+-- indexes on partitions don't change parent. ALTER INDEX ATTACH can change+-- the parent after the fact.+create table idxpart (a int) partition by range (a);+create table idxpart1 partition of idxpart for values from (0) to (100);+create table idxpart2 partition of idxpart for values from (100) to (1000)+ partition by range (a);+create table idxpart21 partition of idxpart2 for values from (100) to (200);+create table idxpart22 partition of idxpart2 for values from (200) to (300);+create index on idxpart22 (a);+create index on only idxpart2 (a);+create index on idxpart (a);+-- Here we expect that idxpart1 and idxpart2 have a new index, but idxpart21+-- does not; also, idxpart22 is detached.+\d idxpart1+\d idxpart2+\d idxpart21+select indexrelid::regclass, indrelid::regclass, indparentidx::regclass+ from pg_index where indexrelid::regclass::text like 'idxpart%'+ order by indrelid::regclass;+alter index idxpart2_a_idx attach partition idxpart22_a_idx;+select indexrelid::regclass, indrelid::regclass, indparentidx::regclass+ from pg_index where indexrelid::regclass::text like 'idxpart%'+ order by indrelid::regclass;+drop table idxpart;++-- When a table is attached a partition and it already has an index, a+-- duplicate index should not get created, but rather the index becomes+-- attached to the parent's index.+create table idxpart (a int, b int, c text) partition by range (a);+create index idxparti on idxpart (a);+create index idxparti2 on idxpart (b, c);+create table idxpart1 (like idxpart including indexes);+\d idxpart1+select relname, relkind, indparentidx::regclass+ from pg_class left join pg_index on (indexrelid = oid)+ where relname like 'idxpart%' order by relname;+alter table idxpart attach partition idxpart1 for values from (0) to (10);+\d idxpart1+select relname, relkind, indparentidx::regclass+ from pg_class left join pg_index on (indexrelid = oid)+ where relname like 'idxpart%' order by relname;+drop table idxpart;++-- Make sure the partition columns are mapped correctly+create table idxpart (a int, b int, c text) partition by range (a);+create index idxparti on idxpart (a);+create index idxparti2 on idxpart (c, b);+create table idxpart1 (c text, a int, b int);+alter table idxpart attach partition idxpart1 for values from (0) to (10);+\d idxpart1+drop table idxpart;++-- Make sure things work if either table has dropped columns+create table idxpart (a int, b int, c int, d int) partition by range (a);+alter table idxpart drop column c;+create index idxparti on idxpart (a);+create index idxparti2 on idxpart (b, d);+create table idxpart1 (like idxpart);+alter table idxpart attach partition idxpart1 for values from (0) to (10);+\d idxpart1+select attrelid::regclass, attname, attnum from pg_attribute+ where attrelid in ('idxpart'::regclass, 'idxpart1'::regclass) and attnum > 0+ order by attrelid::regclass, attnum;+drop table idxpart;++create table idxpart (a int, b int, c int) partition by range (a);+create table idxpart1 (zz int, like idxpart, aa int);+alter table idxpart1 drop column zz, drop column aa;+create index idxparti on idxpart (a);+create index idxparti2 on idxpart (b, c);+alter table idxpart attach partition idxpart1 for values from (0) to (10);+\d idxpart1+select attrelid::regclass, attname, attnum from pg_attribute+ where attrelid in ('idxpart'::regclass, 'idxpart1'::regclass) and attnum > 0+ order by attrelid::regclass, attnum;+drop table idxpart;
--
2.11.0
From 01a710194dc129b711598764e5dddde1f9d14b54 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 6 Nov 2017 17:04:55 +0100
Subject: [PATCH v4 5/5] allow indexes on partitioned tables to be unique
---
src/backend/commands/indexcmds.c | 74 ++++++++++++++--
src/backend/parser/parse_utilcmd.c | 24 -----
src/test/regress/expected/alter_table.out | 8 --
src/test/regress/expected/create_table.out | 12 ---
src/test/regress/expected/indexing.out | 135 ++++++++++++++++++++++++++++-
src/test/regress/sql/alter_table.sql | 2 -
src/test/regress/sql/create_table.sql | 8 --
src/test/regress/sql/indexing.sql | 72 ++++++++++++++-
8 files changed, 269 insertions(+), 66 deletions(-)
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.cindex c289f62b9d..446c58368e 100644--- a/src/backend/commands/indexcmds.c+++ b/src/backend/commands/indexcmds.c@@ -425,20 +425,11 @@ DefineIndex(Oid relationId,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot create index on partitioned table \"%s\" concurrently",
RelationGetRelationName(rel))));
- if (stmt->unique)- ereport(ERROR,- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),- errmsg("cannot create unique index on partitioned table \"%s\"",- RelationGetRelationName(rel))));
if (stmt->excludeOpNames)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot create exclusion constraints on partitioned table \"%s\"",
RelationGetRelationName(rel))));
- if (is_alter_table)- ereport(ERROR,- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),- errmsg("cannot create constraints on partitioned tables")));
}
/*
@@ -635,6 +626,71 @@ DefineIndex(Oid relationId,
index_check_primary_key(rel, indexInfo, is_alter_table);
/*
+ * If this table is partitioned and we're creating a unique index or a+ * primary key, make sure that the indexed columns are part of the+ * partition key. Otherwise it would be possible to violate uniqueness by+ * putting values that ought to be unique in different partitions.+ *+ * We could lift this limitation if we had global indexes, but those have+ * their own problems, so this is a useful feature combination.+ *+ * XXX in some cases rel->rd_partkey is NULL, which caused this code to+ * crash. In what cases can that happen?+ */+ if (partitioned && (stmt->unique || stmt->primary) &&+ (rel->rd_partkey != NULL))+ {+ int i;+ PartitionKey key = rel->rd_partkey;++ /*+ * A partitioned table can have unique indexes, as long as all the+ * columns in the unique key appear in the partition key. Because+ * partitions are non-overlapping, this guarantees that there is a+ * single partition that can contain any given key, and thus the+ * uniqueness is guaranteed globally.+ */++ for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)+ {+ bool found;+ int j;++ /*+ * Expression indexes are not supported yet. We can probably use+ * pull_varattnos() on the expression, and verify that all the+ * vars mentioned correspond to columns of the partition key.+ */+ if (indexInfo->ii_KeyAttrNumbers[i] == 0)+ ereport(ERROR,+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),+ errmsg("UNIQUE indexes containing expressions are not supported")));++ found = false;+ for (j = 0; j < key->partnatts; j++)+ {+ if (indexInfo->ii_KeyAttrNumbers[i] == key->partattrs[j])+ {+ found = true;+ break; /* all good */+ }+ }+ if (!found)+ {+ char *attname;++ attname = NameStr(TupleDescAttr(RelationGetDescr(rel),+ indexInfo->ii_KeyAttrNumbers[i] - 1)->attname);+ ereport(ERROR,+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),+ errmsg("UNIQUE index on table \"%s\" contains column \"%s\" which is not part of the partition key",+ RelationGetRelationName(rel), attname)));+ }+ }+ }+++ /*
* We disallow indexes on system columns other than OID. They would not
* necessarily get updated correctly, and they don't seem useful anyway.
*/
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.cindex 938e73f8ad..364bd4c81d 100644--- a/src/backend/parser/parse_utilcmd.c+++ b/src/backend/parser/parse_utilcmd.c@@ -692,12 +692,6 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
- if (cxt->ispartitioned)- ereport(ERROR,- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),- errmsg("primary key constraints are not supported on partitioned tables"),- parser_errposition(cxt->pstate,- constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -707,12 +701,6 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
- if (cxt->ispartitioned)- ereport(ERROR,- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),- errmsg("unique constraints are not supported on partitioned tables"),- parser_errposition(cxt->pstate,- constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -809,12 +797,6 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
- if (cxt->ispartitioned)- ereport(ERROR,- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),- errmsg("primary key constraints are not supported on partitioned tables"),- parser_errposition(cxt->pstate,- constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -825,12 +807,6 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
- if (cxt->ispartitioned)- ereport(ERROR,- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),- errmsg("unique constraints are not supported on partitioned tables"),- parser_errposition(cxt->pstate,- constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.outindex 5a96b0b5e1..e7cf6ae470 100644--- a/src/test/regress/expected/alter_table.out+++ b/src/test/regress/expected/alter_table.out@@ -3229,14 +3229,6 @@ CREATE TABLE partitioned (
a int,
b int
) PARTITION BY RANGE (a, (a+b+1));
-ALTER TABLE partitioned ADD UNIQUE (a);-ERROR: unique constraints are not supported on partitioned tables-LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);- ^-ALTER TABLE partitioned ADD PRIMARY KEY (a);-ERROR: primary key constraints are not supported on partitioned tables-LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);- ^
ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
ERROR: foreign key constraints are not supported on partitioned tables
LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.outindex 335cd37e18..a89e0c17df 100644--- a/src/test/regress/expected/create_table.out+++ b/src/test/regress/expected/create_table.out@@ -276,12 +276,6 @@ CREATE TABLE partitioned (
) PARTITION BY LIST (a1, a2); -- fail
ERROR: cannot use "list" partition strategy with more than one column
-- unsupported constraint type for partitioned tables
-CREATE TABLE partitioned (- a int PRIMARY KEY-) PARTITION BY RANGE (a);-ERROR: primary key constraints are not supported on partitioned tables-LINE 2: a int PRIMARY KEY- ^
CREATE TABLE pkrel (
a int PRIMARY KEY
);
@@ -293,12 +287,6 @@ LINE 2: a int REFERENCES pkrel(a)
^
DROP TABLE pkrel;
CREATE TABLE partitioned (
- a int UNIQUE-) PARTITION BY RANGE (a);-ERROR: unique constraints are not supported on partitioned tables-LINE 2: a int UNIQUE- ^-CREATE TABLE partitioned (
a int,
EXCLUDE USING gist (a WITH &&)
) PARTITION BY RANGE (a);
diff --git a/src/test/regress/expected/indexing.out b/src/test/regress/expected/indexing.outindex cde29bebb5..7ec8c5e3f7 100644--- a/src/test/regress/expected/indexing.out+++ b/src/test/regress/expected/indexing.out@@ -25,8 +25,6 @@ drop table idxpart;
-- Some unsupported cases
create table idxpart (a int, b int, c text) partition by range (a);
create table idxpart1 partition of idxpart for values from (0) to (10);
-create unique index on idxpart (a);-ERROR: cannot create unique index on partitioned table "idxpart"
create index concurrently on idxpart (a);
ERROR: cannot create index on partitioned table "idxpart" concurrently
drop table idxpart;
@@ -328,3 +326,136 @@ select attrelid::regclass, attname, attnum from pg_attribute
(8 rows)
drop table idxpart;
+--+-- Constraint-related indexes+--+-- Verify that it works to add primary key / unique to partitioned tables+create table idxpart (a int primary key, b int) partition by range (b);+ERROR: UNIQUE index on table "idxpart" contains column "a" which is not part of the partition key+create table idxpart (a int primary key, b int) partition by range (a);+\d idxpart+ Table "public.idxpart"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | not null | + b | integer | | | +Partition key: RANGE (a)+Indexes:+ "idxpart_pkey" PRIMARY KEY, btree (a)++drop table idxpart;+create table idxpart (a int unique, b int) partition by range (a);+\d idxpart+ Table "public.idxpart"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | + b | integer | | | +Partition key: RANGE (a)+Indexes:+ "idxpart_a_key" UNIQUE CONSTRAINT, btree (a)++drop table idxpart;+-- but not other types of index-based constraints+create table idxpart (a int, exclude (a with = )) partition by range (a);+ERROR: exclusion constraints are not supported on partitioned tables+LINE 1: create table idxpart (a int, exclude (a with = )) partition ...+ ^+-- It works to add primary keys after the partitioned table is created+create table idxpart (a int, b int) partition by range (a);+alter table idxpart add primary key (a);+\d idxpart+ Table "public.idxpart"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | not null | + b | integer | | | +Partition key: RANGE (a)+Indexes:+ "idxpart_pkey" PRIMARY KEY, btree (a)++drop table idxpart;+-- It works to add unique constraints after the partitioned table is created+create table idxpart (a int, b int) partition by range (a);+alter table idxpart add unique (a);+\d idxpart+ Table "public.idxpart"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | + b | integer | | | +Partition key: RANGE (a)+Indexes:+ "idxpart_a_key" UNIQUE CONSTRAINT, btree (a)++drop table idxpart;+-- Exclusion constraints cannot be added+create table idxpart (a int, b int) partition by range (a);+alter table idxpart add exclude (a with =);+ERROR: exclusion constraints are not supported on partitioned tables+LINE 1: alter table idxpart add exclude (a with =);+ ^+drop table idxpart;+-- It is an error to add a constraint to columns that are not in the partition+-- key.+create table idxpart (a int, b int, primary key (a, b)) partition by range (a);+ERROR: UNIQUE index on table "idxpart" contains column "b" which is not part of the partition key+create table idxpart (a int, b int, primary key (a, b)) partition by range (b);+ERROR: UNIQUE index on table "idxpart" contains column "a" which is not part of the partition key+create table idxpart (a int, b int, unique (a, b)) partition by range (a);+ERROR: UNIQUE index on table "idxpart" contains column "b" which is not part of the partition key+create table idxpart (a int, b int, unique (a, b)) partition by range (b);+ERROR: UNIQUE index on table "idxpart" contains column "a" which is not part of the partition key+-- but using a partial set of columns is okay+create table idxpart (a int, b int, c int primary key) partition by range (b);+ERROR: UNIQUE index on table "idxpart" contains column "c" which is not part of the partition key+drop table idxpart;+ERROR: table "idxpart" does not exist+create table idxpart (a int, b int, primary key (a)) partition by range (a, b);+drop table idxpart;+create table idxpart (a int, b int, primary key (b)) partition by range (a, b);+drop table idxpart;+create table idxpart (a int, b int, c int unique) partition by range (b);+ERROR: UNIQUE index on table "idxpart" contains column "c" which is not part of the partition key+drop table idxpart;+ERROR: table "idxpart" does not exist+create table idxpart (a int, b int, unique (a)) partition by range (a, b);+drop table idxpart;+create table idxpart (a int, b int, unique (b)) partition by range (a, b);+drop table idxpart;+-- When (sub)partitions are created, they also contain the constraint+create table idxpart (a int, b int, primary key (a, b)) partition by range (a, b);+create table idxpart1 partition of idxpart for values from (1, 1) to (10, 10);+create table idxpart2 partition of idxpart for values from (10, 10) to (20, 20)+ partition by range (b);+create table idxpart21 partition of idxpart2 for values from (10) to (15);+create table idxpart22 partition of idxpart2 for values from (15) to (20);+create table idxpart3 (b int not null, a int not null);+alter table idxpart attach partition idxpart3 for values from (20, 20) to (30, 30);+select conname, contype, conrelid::regclass, conindid::regclass, conkey+ from pg_constraint where conrelid::regclass::text like 'idxpart%'+ order by conname;+ conname | contype | conrelid | conindid | conkey +----------------+---------+-----------+----------------+--------+ idxpart1_pkey | p | idxpart1 | idxpart1_pkey | {1,2}+ idxpart21_pkey | p | idxpart21 | idxpart21_pkey | {1,2}+ idxpart22_pkey | p | idxpart22 | idxpart22_pkey | {1,2}+ idxpart2_pkey | p | idxpart2 | idxpart2_pkey | {1,2}+ idxpart3_pkey | p | idxpart3 | idxpart3_pkey | {2,1}+ idxpart_pkey | p | idxpart | idxpart_pkey | {1,2}+(6 rows)++drop table idxpart;+create table idxpart (a int primary key, b int) partition by range (a);+create table idxpart1 partition of idxpart for values from (1) to (10);+\d idxpart1+ Table "public.idxpart1"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | not null | + b | integer | | | +Partition of: idxpart FOR VALUES FROM (1) TO (10)+Indexes:+ "idxpart1_pkey" PRIMARY KEY, btree (a)++drop table idxpart;diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sqlindex 02a33ca7c4..115ed1efad 100644--- a/src/test/regress/sql/alter_table.sql+++ b/src/test/regress/sql/alter_table.sql@@ -2000,8 +2000,6 @@ CREATE TABLE partitioned (
a int,
b int
) PARTITION BY RANGE (a, (a+b+1));
-ALTER TABLE partitioned ADD UNIQUE (a);-ALTER TABLE partitioned ADD PRIMARY KEY (a);
ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sqlindex b77b476436..673db6b77d 100644--- a/src/test/regress/sql/create_table.sql+++ b/src/test/regress/sql/create_table.sql@@ -294,10 +294,6 @@ CREATE TABLE partitioned (
) PARTITION BY LIST (a1, a2); -- fail
-- unsupported constraint type for partitioned tables
-CREATE TABLE partitioned (- a int PRIMARY KEY-) PARTITION BY RANGE (a);-
CREATE TABLE pkrel (
a int PRIMARY KEY
);
@@ -307,10 +303,6 @@ CREATE TABLE partitioned (
DROP TABLE pkrel;
CREATE TABLE partitioned (
- a int UNIQUE-) PARTITION BY RANGE (a);--CREATE TABLE partitioned (
a int,
EXCLUDE USING gist (a WITH &&)
) PARTITION BY RANGE (a);
diff --git a/src/test/regress/sql/indexing.sql b/src/test/regress/sql/indexing.sqlindex 53a28f120f..0831837560 100644--- a/src/test/regress/sql/indexing.sql+++ b/src/test/regress/sql/indexing.sql@@ -14,7 +14,6 @@ drop table idxpart;
-- Some unsupported cases
create table idxpart (a int, b int, c text) partition by range (a);
create table idxpart1 partition of idxpart for values from (0) to (10);
-create unique index on idxpart (a);
create index concurrently on idxpart (a);
drop table idxpart;
@@ -140,3 +139,74 @@ select attrelid::regclass, attname, attnum from pg_attribute
where attrelid in ('idxpart'::regclass, 'idxpart1'::regclass) and attnum > 0
order by attrelid::regclass, attnum;
drop table idxpart;
++--+-- Constraint-related indexes+--++-- Verify that it works to add primary key / unique to partitioned tables+create table idxpart (a int primary key, b int) partition by range (b);+create table idxpart (a int primary key, b int) partition by range (a);+\d idxpart+drop table idxpart;+create table idxpart (a int unique, b int) partition by range (a);+\d idxpart+drop table idxpart;+-- but not other types of index-based constraints+create table idxpart (a int, exclude (a with = )) partition by range (a);++-- It works to add primary keys after the partitioned table is created+create table idxpart (a int, b int) partition by range (a);+alter table idxpart add primary key (a);+\d idxpart+drop table idxpart;++-- It works to add unique constraints after the partitioned table is created+create table idxpart (a int, b int) partition by range (a);+alter table idxpart add unique (a);+\d idxpart+drop table idxpart;++-- Exclusion constraints cannot be added+create table idxpart (a int, b int) partition by range (a);+alter table idxpart add exclude (a with =);+drop table idxpart;++-- It is an error to add a constraint to columns that are not in the partition+-- key.+create table idxpart (a int, b int, primary key (a, b)) partition by range (a);+create table idxpart (a int, b int, primary key (a, b)) partition by range (b);+create table idxpart (a int, b int, unique (a, b)) partition by range (a);+create table idxpart (a int, b int, unique (a, b)) partition by range (b);+-- but using a partial set of columns is okay+create table idxpart (a int, b int, c int primary key) partition by range (b);+drop table idxpart;+create table idxpart (a int, b int, primary key (a)) partition by range (a, b);+drop table idxpart;+create table idxpart (a int, b int, primary key (b)) partition by range (a, b);+drop table idxpart;+create table idxpart (a int, b int, c int unique) partition by range (b);+drop table idxpart;+create table idxpart (a int, b int, unique (a)) partition by range (a, b);+drop table idxpart;+create table idxpart (a int, b int, unique (b)) partition by range (a, b);+drop table idxpart;++-- When (sub)partitions are created, they also contain the constraint+create table idxpart (a int, b int, primary key (a, b)) partition by range (a, b);+create table idxpart1 partition of idxpart for values from (1, 1) to (10, 10);+create table idxpart2 partition of idxpart for values from (10, 10) to (20, 20)+ partition by range (b);+create table idxpart21 partition of idxpart2 for values from (10) to (15);+create table idxpart22 partition of idxpart2 for values from (15) to (20);+create table idxpart3 (b int not null, a int not null);+alter table idxpart attach partition idxpart3 for values from (20, 20) to (30, 30);+select conname, contype, conrelid::regclass, conindid::regclass, conkey+ from pg_constraint where conrelid::regclass::text like 'idxpart%'+ order by conname;+drop table idxpart;++create table idxpart (a int primary key, b int) partition by range (a);+create table idxpart1 partition of idxpart for values from (1) to (10);+\d idxpart1+drop table idxpart;
--
2.11.0
Attachments:
[text/plain] v4-0001-Simplify-index_-constraint_-create-API.patch (14.9K, ../20171113175535.bvlfra6lm5rnoja2@alvherre.pgsql/2-v4-0001-Simplify-index_-constraint_-create-API.patch)
download | inline diff:
From a485e8710110659d76cb377469a5eee75c64fcd0 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 16 Oct 2017 12:01:12 +0200
Subject: [PATCH v4 1/5] Simplify index_[constraint_]create API
Instead of passing large swaths of boolean arguments, define some flags
that can be used in a bitmask. This makes it easier not only to figure
out what each call site is doing, but also to add additional behavioral
flags.
The flags are split in two -- one set for index_create directly and
another for constraints. index_create() itself receives both, and then
passes down the latter to index_constraint_create, which can also be
called standalone.
Discussion: https://postgr.es/m/20171023151251.j75uoe27gajdjmlm@alvherre.pgsql
---
src/backend/catalog/index.c | 101 +++++++++++++++++++--------------------
src/backend/catalog/toasting.c | 3 +-
src/backend/commands/indexcmds.c | 33 +++++++++----
src/backend/commands/tablecmds.c | 13 +++--
src/include/catalog/index.h | 29 ++++++-----
5 files changed, 100 insertions(+), 79 deletions(-)
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.cindex c7b2f031f0..17faeffada 100644--- a/src/backend/catalog/index.c+++ b/src/backend/catalog/index.c@@ -680,19 +680,25 @@ UpdateIndexRelation(Oid indexoid,
* classObjectId: array of index opclass OIDs, one per index column
* coloptions: array of per-index-column indoption settings
* reloptions: AM-specific options
- * isprimary: index is a PRIMARY KEY- * isconstraint: index is owned by PRIMARY KEY, UNIQUE, or EXCLUSION constraint- * deferrable: constraint is DEFERRABLE- * initdeferred: constraint is INITIALLY DEFERRED+ * flags: bitmask that can include any combination of these bits:+ * INDEX_CREATE_IS_PRIMARY+ * the index is a primary key+ * INDEX_CREATE_ADD_CONSTRAINT:+ * invoke index_constraint_create also+ * INDEX_CREATE_SKIP_BUILD:+ * skip the index_build() step for the moment; caller must do it+ * later (typically via reindex_index())+ * INDEX_CREATE_CONCURRENT:+ * do not lock the table against writers. The index will be+ * marked "invalid" and the caller must take additional steps+ * to fix it up.+ * INDEX_CREATE_IF_NOT_EXISTS:+ * do not throw an error if a relation with the same name+ * already exists.+ * 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
- * skip_build: true to skip the index_build() step for the moment; caller- * must do it later (typically via reindex_index())- * concurrent: if true, do not lock the table against writers. The index- * will be marked "invalid" and the caller must take additional steps- * to fix it up.
* is_internal: if true, post creation hook for new index
- * if_not_exists: if true, do not throw an error if a relation with- * the same name already exists.
*
* Returns the OID of the created index.
*/
@@ -709,15 +715,10 @@ index_create(Relation heapRelation,
Oid *classObjectId,
int16 *coloptions,
Datum reloptions,
- bool isprimary,- bool isconstraint,- bool deferrable,- bool initdeferred,+ uint16 flags,+ uint16 constr_flags,
bool allow_system_table_mods,
- bool skip_build,- bool concurrent,- bool is_internal,- bool if_not_exists)+ bool is_internal)
{
Oid heapRelationId = RelationGetRelid(heapRelation);
Relation pg_class;
@@ -729,6 +730,12 @@ index_create(Relation heapRelation,
Oid namespaceId;
int i;
char relpersistence;
+ bool isprimary = flags & INDEX_CREATE_IS_PRIMARY;+ bool concurrent = flags & INDEX_CREATE_CONCURRENT;++ /* constraint flags can only be set when constraint is requested */+ Assert((constr_flags == 0) ||+ ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0));
is_exclusion = (indexInfo->ii_ExclusionOps != NULL);
@@ -794,7 +801,7 @@ index_create(Relation heapRelation,
if (get_relname_relid(indexRelationName, namespaceId))
{
- if (if_not_exists)+ if ((flags & INDEX_CREATE_IF_NOT_EXISTS) != 0)
{
ereport(NOTICE,
(errcode(ERRCODE_DUPLICATE_TABLE),
@@ -917,7 +924,7 @@ index_create(Relation heapRelation,
UpdateIndexRelation(indexRelationId, heapRelationId, indexInfo,
collationObjectId, classObjectId, coloptions,
isprimary, is_exclusion,
- !deferrable,+ (constr_flags & INDEX_CONSTR_CREATE_DEFERRABLE) == 0,
!concurrent);
/*
@@ -943,7 +950,7 @@ index_create(Relation heapRelation,
myself.objectId = indexRelationId;
myself.objectSubId = 0;
- if (isconstraint)+ if ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0)
{
char constraintType;
@@ -964,11 +971,7 @@ index_create(Relation heapRelation,
indexInfo,
indexRelationName,
constraintType,
- deferrable,- initdeferred,- false, /* already marked primary */- false, /* pg_index entry is OK */- false, /* no old dependencies */+ constr_flags,
allow_system_table_mods,
is_internal);
}
@@ -1005,10 +1008,6 @@ index_create(Relation heapRelation,
recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
}
-- /* Non-constraint indexes can't be deferrable */- Assert(!deferrable);- Assert(!initdeferred);
}
/* Store dependency on collations */
@@ -1059,9 +1058,7 @@ index_create(Relation heapRelation,
else
{
/* Bootstrap mode - assert we weren't asked for constraint support */
- Assert(!isconstraint);- Assert(!deferrable);- Assert(!initdeferred);+ Assert((flags & INDEX_CREATE_ADD_CONSTRAINT) == 0);
}
/* Post creation hook for new index */
@@ -1089,15 +1086,16 @@ index_create(Relation heapRelation,
* If this is bootstrap (initdb) time, then we don't actually fill in the
* index yet. We'll be creating more indexes and classes later, so we
* delay filling them in until just before we're done with bootstrapping.
- * Similarly, if the caller specified skip_build then filling the index is- * delayed till later (ALTER TABLE can save work in some cases with this).- * Otherwise, we call the AM routine that constructs the index.+ * Similarly, if the caller specified to skip the build then filling the+ * index is delayed till later (ALTER TABLE can save work in some cases+ * with this). Otherwise, we call the AM routine that constructs the+ * index.
*/
if (IsBootstrapProcessingMode())
{
index_register(heapRelationId, indexRelationId, indexInfo);
}
- else if (skip_build)+ else if ((flags & INDEX_CREATE_SKIP_BUILD) != 0)
{
/*
* Caller is responsible for filling the index later on. However,
@@ -1137,12 +1135,13 @@ index_create(Relation heapRelation,
* constraintName: what it say (generally, should match name of index)
* constraintType: one of CONSTRAINT_PRIMARY, CONSTRAINT_UNIQUE, or
* CONSTRAINT_EXCLUSION
- * deferrable: constraint is DEFERRABLE- * initdeferred: constraint is INITIALLY DEFERRED- * mark_as_primary: if true, set flags to mark index as primary key- * update_pgindex: if true, update pg_index row (else caller's done that)- * remove_old_dependencies: if true, remove existing dependencies of index- * on table's columns+ * flags: bitmask that can include any combination of these bits:+ * INDEX_CONSTR_CREATE_MARK_AS_PRIMARY: index is a PRIMARY KEY+ * INDEX_CONSTR_CREATE_DEFERRABLE: constraint is DEFERRABLE+ * INDEX_CONSTR_CREATE_INIT_DEFERRED: constraint is INITIALLY DEFERRED+ * INDEX_CONSTR_CREATE_UPDATE_INDEX: update the pg_index row+ * INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS: remove existing dependencies+ * of index on table's columns
* allow_system_table_mods: allow table to be a system catalog
* is_internal: index is constructed due to internal process
*/
@@ -1152,11 +1151,7 @@ index_constraint_create(Relation heapRelation,
IndexInfo *indexInfo,
const char *constraintName,
char constraintType,
- bool deferrable,- bool initdeferred,- bool mark_as_primary,- bool update_pgindex,- bool remove_old_dependencies,+ uint16 constr_flags,
bool allow_system_table_mods,
bool is_internal)
{
@@ -1164,6 +1159,9 @@ index_constraint_create(Relation heapRelation,
ObjectAddress myself,
referenced;
Oid conOid;
+ bool deferrable = constr_flags & INDEX_CONSTR_CREATE_DEFERRABLE;+ bool initdeferred = constr_flags & INDEX_CONSTR_CREATE_INIT_DEFERRED;+ bool mark_as_primary = constr_flags & INDEX_CONSTR_CREATE_MARK_AS_PRIMARY;
/* constraint creation support doesn't work while bootstrapping */
Assert(!IsBootstrapProcessingMode());
@@ -1190,7 +1188,7 @@ index_constraint_create(Relation heapRelation,
* has any expressions or predicate, but we'd never be turning such an
* index into a UNIQUE or PRIMARY KEY constraint.
*/
- if (remove_old_dependencies)+ if (constr_flags & INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS)
deleteDependencyRecordsForClass(RelationRelationId, indexRelationId,
RelationRelationId, DEPENDENCY_AUTO);
@@ -1295,7 +1293,8 @@ index_constraint_create(Relation heapRelation,
* is a risk that concurrent readers of the table will miss seeing this
* index at all.
*/
- if (update_pgindex && (mark_as_primary || deferrable))+ if ((constr_flags & INDEX_CONSTR_CREATE_UPDATE_INDEX) &&+ (mark_as_primary || deferrable))
{
Relation pg_index;
HeapTuple indexTuple;
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.cindex 6f517bbcda..539ca79ad3 100644--- a/src/backend/catalog/toasting.c+++ b/src/backend/catalog/toasting.c@@ -333,8 +333,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
BTREE_AM_OID,
rel->rd_rel->reltablespace,
collationObjectId, classObjectId, coloptions, (Datum) 0,
- true, false, false, false,- true, false, false, true, false);+ INDEX_CREATE_IS_PRIMARY, 0, true, true);
heap_close(toast_rel, NoLock);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.cindex 89114af119..963cfedcb4 100644--- a/src/backend/commands/indexcmds.c+++ b/src/backend/commands/indexcmds.c@@ -333,6 +333,8 @@ DefineIndex(Oid relationId,
Datum reloptions;
int16 *coloptions;
IndexInfo *indexInfo;
+ uint16 flags;+ uint16 constr_flags;
int numberOfAttributes;
TransactionId limitXmin;
VirtualTransactionId *old_snapshots;
@@ -661,20 +663,35 @@ DefineIndex(Oid relationId,
Assert(!OidIsValid(stmt->oldNode) || (skip_build && !stmt->concurrent));
/*
- * Make the catalog entries for the index, including constraints. Then, if- * not skip_build || concurrent, actually build the index.+ * 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.
*/
+ flags = constr_flags = 0;+ if (stmt->isconstraint)+ flags |= INDEX_CREATE_ADD_CONSTRAINT;+ if (skip_build || stmt->concurrent)+ flags |= INDEX_CREATE_SKIP_BUILD;+ if (stmt->if_not_exists)+ flags |= INDEX_CREATE_IF_NOT_EXISTS;+ if (stmt->concurrent)+ flags |= INDEX_CREATE_CONCURRENT;+ if (stmt->primary)+ flags |= INDEX_CREATE_IS_PRIMARY;++ if (stmt->deferrable)+ constr_flags |= INDEX_CONSTR_CREATE_DEFERRABLE;+ if (stmt->initdeferred)+ constr_flags |= INDEX_CONSTR_CREATE_INIT_DEFERRED;+
indexRelationId =
index_create(rel, indexRelationName, indexRelationId, stmt->oldNode,
indexInfo, indexColNames,
accessMethodId, tablespaceId,
collationObjectId, classObjectId,
- coloptions, reloptions, stmt->primary,- stmt->isconstraint, stmt->deferrable, stmt->initdeferred,- allowSystemTableMods,- skip_build || stmt->concurrent,- stmt->concurrent, !check_rights,- stmt->if_not_exists);+ coloptions, reloptions,+ flags, constr_flags,+ allowSystemTableMods, !check_rights);
ObjectAddressSet(address, RelationRelationId, indexRelationId);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.cindex 9c66aa75ed..a723d98666 100644--- a/src/backend/commands/tablecmds.c+++ b/src/backend/commands/tablecmds.c@@ -6836,6 +6836,7 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
char *constraintName;
char constraintType;
ObjectAddress address;
+ uint16 flags;
Assert(IsA(stmt, IndexStmt));
Assert(OidIsValid(index_oid));
@@ -6880,16 +6881,18 @@ ATExecAddIndexConstraint(AlteredTableInfo *tab, Relation rel,
constraintType = CONSTRAINT_UNIQUE;
/* Create the catalog entries for the constraint */
+ flags = INDEX_CONSTR_CREATE_UPDATE_INDEX |+ INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS |+ (stmt->initdeferred ? INDEX_CONSTR_CREATE_INIT_DEFERRED : 0) |+ (stmt->deferrable ? INDEX_CONSTR_CREATE_DEFERRABLE : 0) |+ (stmt->primary ? INDEX_CONSTR_CREATE_MARK_AS_PRIMARY : 0);+
address = index_constraint_create(rel,
index_oid,
indexInfo,
constraintName,
constraintType,
- stmt->deferrable,- stmt->initdeferred,- stmt->primary,- true, /* update pg_index */- true, /* remove old dependencies */+ flags,
allowSystemTableMods,
false); /* is_internal */
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.hindex 1d4ec09f8f..ab1f9ef1bd 100644--- a/src/include/catalog/index.h+++ b/src/include/catalog/index.h@@ -42,6 +42,12 @@ extern void index_check_primary_key(Relation heapRel,
IndexInfo *indexInfo,
bool is_alter_table);
+#define INDEX_CREATE_IS_PRIMARY (1 << 0)+#define INDEX_CREATE_ADD_CONSTRAINT (1 << 1)+#define INDEX_CREATE_SKIP_BUILD (1 << 2)+#define INDEX_CREATE_CONCURRENT (1 << 3)+#define INDEX_CREATE_IF_NOT_EXISTS (1 << 4)+
extern Oid index_create(Relation heapRelation,
const char *indexRelationName,
Oid indexRelationId,
@@ -54,26 +60,23 @@ extern Oid index_create(Relation heapRelation,
Oid *classObjectId,
int16 *coloptions,
Datum reloptions,
- bool isprimary,- bool isconstraint,- bool deferrable,- bool initdeferred,+ uint16 flags,+ uint16 constr_flags,
bool allow_system_table_mods,
- bool skip_build,- bool concurrent,- bool is_internal,- bool if_not_exists);+ bool is_internal);++#define INDEX_CONSTR_CREATE_MARK_AS_PRIMARY (1 << 0)+#define INDEX_CONSTR_CREATE_DEFERRABLE (1 << 1)+#define INDEX_CONSTR_CREATE_INIT_DEFERRED (1 << 2)+#define INDEX_CONSTR_CREATE_UPDATE_INDEX (1 << 3)+#define INDEX_CONSTR_CREATE_REMOVE_OLD_DEPS (1 << 4)
extern ObjectAddress index_constraint_create(Relation heapRelation,
Oid indexRelationId,
IndexInfo *indexInfo,
const char *constraintName,
char constraintType,
- bool deferrable,- bool initdeferred,- bool mark_as_primary,- bool update_pgindex,- bool remove_old_dependencies,+ uint16 constr_flags,
bool allow_system_table_mods,
bool is_internal);
--
2.11.0
[text/plain] v4-0002-Get-rid-of-copy_partition_key.patch (4.2K, ../20171113175535.bvlfra6lm5rnoja2@alvherre.pgsql/3-v4-0002-Get-rid-of-copy_partition_key.patch)
download | inline diff:
From 149e7e7bd3e68c2b2305df39189a193c3f29e262 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 26 Oct 2017 11:15:39 +0200
Subject: [PATCH v4 2/5] Get rid of copy_partition_key
Instead, allocate the objects in a temporary context right from the
start, and only make it permanent once no errors can occur anymore.
Reviewed-by: Robert Haas, Tom Lane
Discussion: https://postgr.es/m/20171027172730.eh2domlkpn4ja62m@alvherre.pgsql
---
src/backend/utils/cache/relcache.c | 69 +++++---------------------------------
1 file changed, 8 insertions(+), 61 deletions(-)
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.cindex 1908420d82..59280cd8bb 100644--- a/src/backend/utils/cache/relcache.c+++ b/src/backend/utils/cache/relcache.c@@ -262,7 +262,6 @@ static Relation AllocateRelationDesc(Form_pg_class relp);
static void RelationParseRelOptions(Relation relation, HeapTuple tuple);
static void RelationBuildTupleDesc(Relation relation);
static void RelationBuildPartitionKey(Relation relation);
-static PartitionKey copy_partition_key(PartitionKey fromkey);
static Relation RelationBuildDesc(Oid targetRelId, bool insertIt);
static void RelationInitPhysicalAddr(Relation relation);
static void load_critical_index(Oid indexoid, Oid heapoid);
@@ -846,6 +845,11 @@ RelationBuildPartitionKey(Relation relation)
if (!HeapTupleIsValid(tuple))
return;
+ partkeycxt = AllocSetContextCreate(CurTransactionContext,+ RelationGetRelationName(relation),+ ALLOCSET_SMALL_SIZES);+ oldcxt = MemoryContextSwitchTo(partkeycxt);+
key = (PartitionKey) palloc0(sizeof(PartitionKeyData));
/* Fixed-length attributes */
@@ -983,71 +987,14 @@ RelationBuildPartitionKey(Relation relation)
ReleaseSysCache(tuple);
- /* Success --- now copy to the cache memory */- partkeycxt = AllocSetContextCreate(CacheMemoryContext,- RelationGetRelationName(relation),- ALLOCSET_SMALL_SIZES);+ /* Success --- make the relcache point to the newly constructed key */+ MemoryContextSetParent(partkeycxt, CacheMemoryContext);
relation->rd_partkeycxt = partkeycxt;
- oldcxt = MemoryContextSwitchTo(relation->rd_partkeycxt);- relation->rd_partkey = copy_partition_key(key);+ relation->rd_partkey = key;
MemoryContextSwitchTo(oldcxt);
}
/*
- * copy_partition_key- *- * The copy is allocated in the current memory context.- */-static PartitionKey-copy_partition_key(PartitionKey fromkey)-{- PartitionKey newkey;- int n;-- newkey = (PartitionKey) palloc(sizeof(PartitionKeyData));-- newkey->strategy = fromkey->strategy;- newkey->partnatts = n = fromkey->partnatts;-- newkey->partattrs = (AttrNumber *) palloc(n * sizeof(AttrNumber));- memcpy(newkey->partattrs, fromkey->partattrs, n * sizeof(AttrNumber));-- newkey->partexprs = copyObject(fromkey->partexprs);-- newkey->partopfamily = (Oid *) palloc(n * sizeof(Oid));- memcpy(newkey->partopfamily, fromkey->partopfamily, n * sizeof(Oid));-- newkey->partopcintype = (Oid *) palloc(n * sizeof(Oid));- memcpy(newkey->partopcintype, fromkey->partopcintype, n * sizeof(Oid));-- newkey->partsupfunc = (FmgrInfo *) palloc(n * sizeof(FmgrInfo));- memcpy(newkey->partsupfunc, fromkey->partsupfunc, n * sizeof(FmgrInfo));-- newkey->partcollation = (Oid *) palloc(n * sizeof(Oid));- memcpy(newkey->partcollation, fromkey->partcollation, n * sizeof(Oid));-- newkey->parttypid = (Oid *) palloc(n * sizeof(Oid));- memcpy(newkey->parttypid, fromkey->parttypid, n * sizeof(Oid));-- newkey->parttypmod = (int32 *) palloc(n * sizeof(int32));- memcpy(newkey->parttypmod, fromkey->parttypmod, n * sizeof(int32));-- newkey->parttyplen = (int16 *) palloc(n * sizeof(int16));- memcpy(newkey->parttyplen, fromkey->parttyplen, n * sizeof(int16));-- newkey->parttypbyval = (bool *) palloc(n * sizeof(bool));- memcpy(newkey->parttypbyval, fromkey->parttypbyval, n * sizeof(bool));-- newkey->parttypalign = (char *) palloc(n * sizeof(bool));- memcpy(newkey->parttypalign, fromkey->parttypalign, n * sizeof(char));-- newkey->parttypcoll = (Oid *) palloc(n * sizeof(Oid));- memcpy(newkey->parttypcoll, fromkey->parttypcoll, n * sizeof(Oid));-- return newkey;-}--/*
* equalRuleLocks
*
* Determine whether two RuleLocks are equivalent
--
2.11.0
[text/plain] v4-0003-export-generateClonedIndexStmt.patch (6.6K, ../20171113175535.bvlfra6lm5rnoja2@alvherre.pgsql/4-v4-0003-export-generateClonedIndexStmt.patch)
download | inline diff:
From a1f384e1b234b020705a01f47a93bca3ede14210 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 23 Oct 2017 10:18:38 +0200
Subject: [PATCH v4 3/5] export generateClonedIndexStmt
---
src/backend/nodes/copyfuncs.c | 1 +
src/backend/nodes/equalfuncs.c | 1 +
src/backend/nodes/outfuncs.c | 1 +
src/backend/parser/gram.y | 1 +
src/backend/parser/parse_utilcmd.c | 19 +++++++++++--------
src/include/nodes/parsenodes.h | 4 ++++
src/include/parser/parse_utilcmd.h | 3 +++
7 files changed, 22 insertions(+), 8 deletions(-)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.cindex cadd253ef1..61f14a0f5a 100644--- a/src/backend/nodes/copyfuncs.c+++ b/src/backend/nodes/copyfuncs.c@@ -3365,6 +3365,7 @@ _copyIndexStmt(const IndexStmt *from)
COPY_STRING_FIELD(idxname);
COPY_NODE_FIELD(relation);
+ COPY_SCALAR_FIELD(relationId);
COPY_STRING_FIELD(accessMethod);
COPY_STRING_FIELD(tableSpace);
COPY_NODE_FIELD(indexParams);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.cindex 2866fd7b4a..788ce9c756 100644--- a/src/backend/nodes/equalfuncs.c+++ b/src/backend/nodes/equalfuncs.c@@ -1324,6 +1324,7 @@ _equalIndexStmt(const IndexStmt *a, const IndexStmt *b)
{
COMPARE_STRING_FIELD(idxname);
COMPARE_NODE_FIELD(relation);
+ COMPARE_SCALAR_FIELD(relationId);
COMPARE_STRING_FIELD(accessMethod);
COMPARE_STRING_FIELD(tableSpace);
COMPARE_NODE_FIELD(indexParams);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.cindex 291d1eeb46..b428825894 100644--- a/src/backend/nodes/outfuncs.c+++ b/src/backend/nodes/outfuncs.c@@ -2645,6 +2645,7 @@ _outIndexStmt(StringInfo str, const IndexStmt *node)
WRITE_STRING_FIELD(idxname);
WRITE_NODE_FIELD(relation);
+ WRITE_OID_FIELD(relationId);
WRITE_STRING_FIELD(accessMethod);
WRITE_STRING_FIELD(tableSpace);
WRITE_NODE_FIELD(indexParams);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.yindex c301ca465d..78cbf8029c 100644--- a/src/backend/parser/gram.y+++ b/src/backend/parser/gram.y@@ -7236,6 +7236,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
n->concurrent = $4;
n->idxname = $5;
n->relation = $7;
+ n->relationId = InvalidOid;
n->accessMethod = $8;
n->indexParams = $10;
n->options = $12;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.cindex 8461da490a..f9e8f6da0d 100644--- a/src/backend/parser/parse_utilcmd.c+++ b/src/backend/parser/parse_utilcmd.c@@ -117,9 +117,6 @@ static void transformTableLikeClause(CreateStmtContext *cxt,
TableLikeClause *table_like_clause);
static void transformOfType(CreateStmtContext *cxt,
TypeName *ofTypename);
-static IndexStmt *generateClonedIndexStmt(CreateStmtContext *cxt,- Relation source_idx,- const AttrNumber *attmap, int attmap_length);
static List *get_collation(Oid collation, Oid actual_datatype);
static List *get_opclass(Oid opclass, Oid actual_datatype);
static void transformIndexConstraints(CreateStmtContext *cxt);
@@ -1173,7 +1170,8 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
parent_index = index_open(parent_index_oid, AccessShareLock);
/* Build CREATE INDEX statement to recreate the parent_index */
- index_stmt = generateClonedIndexStmt(cxt, parent_index,+ index_stmt = generateClonedIndexStmt(cxt->relation, InvalidOid,+ parent_index,
attmap, tupleDesc->natts);
/* Copy comment on index, if requested */
@@ -1251,10 +1249,12 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
/*
* Generate an IndexStmt node using information from an already existing index
- * "source_idx". Attribute numbers should be adjusted according to attmap.+ * "source_idx", for the rel identified either by heapRel or heapRelid.+ *+ * Attribute numbers should be adjusted according to attmap.
*/
-static IndexStmt *-generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,+IndexStmt *+generateClonedIndexStmt(RangeVar *heapRel, Oid heapRelid, Relation source_idx,
const AttrNumber *attmap, int attmap_length)
{
Oid source_relid = RelationGetRelid(source_idx);
@@ -1275,6 +1275,8 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
Datum datum;
bool isnull;
+ Assert((heapRel != NULL) ^ OidIsValid(heapRelid));+
/*
* Fetch pg_class tuple of source index. We can't use the copy in the
* relcache entry because it doesn't include optional fields.
@@ -1310,7 +1312,8 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
/* Begin building the IndexStmt */
index = makeNode(IndexStmt);
- index->relation = cxt->relation;+ index->relation = heapRel;+ index->relationId = heapRelid;
index->accessMethod = pstrdup(NameStr(amrec->amname));
if (OidIsValid(idxrelrec->reltablespace))
index->tableSpace = get_tablespace_name(idxrelrec->reltablespace);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.hindex 34d6afc80f..b4ce3548dc 100644--- a/src/include/nodes/parsenodes.h+++ b/src/include/nodes/parsenodes.h@@ -2698,6 +2698,9 @@ typedef struct FetchStmt
* index, just a UNIQUE/PKEY constraint using an existing index. isconstraint
* must always be true in this case, and the fields describing the index
* properties are empty.
+ *+ * The relation to build the index on can be represented either by name+ * or by OID.
* ----------------------
*/
typedef struct IndexStmt
@@ -2705,6 +2708,7 @@ typedef struct IndexStmt
NodeTag type;
char *idxname; /* name of new index, or NULL for default */
RangeVar *relation; /* relation to build index on */
+ Oid relationId; /* OID of relation to build index on */
char *accessMethod; /* name of access method (eg. btree) */
char *tableSpace; /* tablespace, or NULL for default */
List *indexParams; /* columns to index: a list of IndexElem */
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.hindex e749432ef0..599f0e8e29 100644--- a/src/include/parser/parse_utilcmd.h+++ b/src/include/parser/parse_utilcmd.h@@ -27,5 +27,8 @@ extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
extern List *transformCreateSchemaStmt(CreateSchemaStmt *stmt);
extern PartitionBoundSpec *transformPartitionBound(ParseState *pstate, Relation parent,
PartitionBoundSpec *spec);
+extern IndexStmt *generateClonedIndexStmt(RangeVar *heapRel, Oid heapOid,+ Relation source_idx,+ const AttrNumber *attmap, int attmap_length);
#endif /* PARSE_UTILCMD_H */
--
2.11.0
[text/plain] v4-0004-Allow-indexes-on-partitioned-tables.patch (109.4K, ../20171113175535.bvlfra6lm5rnoja2@alvherre.pgsql/5-v4-0004-Allow-indexes-on-partitioned-tables.patch)
download | inline diff:
From 9b99656bbc5ac073871c491443f7577625cffcb3 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri, 6 Oct 2017 18:05:50 +0200
Subject: [PATCH v4 4/5] Allow indexes on partitioned tables
---
doc/src/sgml/ddl.sgml | 9 +-
doc/src/sgml/ref/alter_index.sgml | 28 ++-
doc/src/sgml/ref/create_index.sgml | 27 ++-
src/backend/access/common/reloptions.c | 1 +
src/backend/access/heap/heapam.c | 9 +-
src/backend/access/index/indexam.c | 3 +-
src/backend/bootstrap/bootparse.y | 2 +
src/backend/catalog/aclchk.c | 9 +-
src/backend/catalog/dependency.c | 3 +-
src/backend/catalog/heap.c | 1 +
src/backend/catalog/index.c | 101 ++++++++-
src/backend/catalog/objectaddress.c | 5 +-
src/backend/catalog/pg_depend.c | 13 +-
src/backend/catalog/toasting.c | 1 +
src/backend/commands/indexcmds.c | 264 ++++++++++++++++++++--
src/backend/commands/tablecmds.c | 353 +++++++++++++++++++++++++++++-
src/backend/parser/gram.y | 44 +++-
src/backend/parser/parse_utilcmd.c | 45 +++-
src/backend/tcop/utility.c | 9 +
src/backend/utils/adt/amutils.c | 3 +-
src/backend/utils/adt/ruleutils.c | 4 +-
src/backend/utils/cache/relcache.c | 30 ++-
src/bin/pg_dump/common.c | 70 ++++++
src/bin/pg_dump/pg_dump.c | 57 ++++-
src/bin/pg_dump/pg_dump.h | 4 +
src/bin/psql/describe.c | 20 +-
src/include/catalog/catversion.h | 2 +-
src/include/catalog/index.h | 6 +
src/include/catalog/pg_class.h | 1 +
src/include/catalog/pg_index.h | 38 ++--
src/include/commands/defrem.h | 3 +-
src/include/nodes/parsenodes.h | 5 +-
src/test/regress/expected/alter_table.out | 4 +-
src/test/regress/expected/indexing.out | 330 ++++++++++++++++++++++++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/serial_schedule | 1 +
src/test/regress/sql/indexing.sql | 142 ++++++++++++
37 files changed, 1538 insertions(+), 111 deletions(-)
create mode 100644 src/test/regress/expected/indexing.out
create mode 100644 src/test/regress/sql/indexing.sql
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgmlindex daba66c187..9282dfa352 100644--- a/doc/src/sgml/ddl.sgml+++ b/doc/src/sgml/ddl.sgml@@ -2922,7 +2922,9 @@ VALUES ('Albany', NULL, NULL, 'NY');
Partitions may themselves be defined as partitioned tables, using what is
called <firstterm>sub-partitioning</firstterm>. Partitions may have their
own indexes, constraints and default values, distinct from those of other
- partitions. Indexes must be created separately for each partition. See+ partitions. Indexes may be created separately for each partition, but+ any index created on the partitioned table will be inherited by its+ partitions. See
<xref linkend="sql-createtable"> for more details on creating partitioned
tables and partitions.
</para>
@@ -3268,9 +3270,8 @@ ALTER TABLE measurement ATTACH PARTITION measurement_y2008m02
<itemizedlist>
<listitem>
<para>
- There is no facility available to create the matching indexes on all- partitions automatically. Indexes must be added to each partition with- separate commands. This also means that there is no way to create a+ While index creation is supported, constraints are not.+ This means that there is no way to create a
primary key, unique constraint, or exclusion constraint spanning all
partitions; it is only possible to constrain each leaf partition
individually.
diff --git a/doc/src/sgml/ref/alter_index.sgml b/doc/src/sgml/ref/alter_index.sgmlindex 5d0b792e50..e701103987 100644--- a/doc/src/sgml/ref/alter_index.sgml+++ b/doc/src/sgml/ref/alter_index.sgml@@ -23,7 +23,9 @@ PostgreSQL documentation
<synopsis>
ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> RENAME TO <replaceable class="parameter">new_name</replaceable>
ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> SET TABLESPACE <replaceable class="parameter">tablespace_name</replaceable>
-ALTER INDEX <replaceable class="parameter">name</replaceable> DEPENDS ON EXTENSION <replaceable class="parameter">extension_name</replaceable>+ALTER INDEX <replaceable class="parameter">name</replaceable> ATTACH <replaceable class="parameter">index_name</replaceable>+ALTER INDEX <replaceable class="parameter">name</replaceable> DETACH <replaceable class="parameter">index_name</replaceable>+ALTER INDEX DEPENDS ON EXTENSION <replaceable class="parameter">extension_name</replaceable>
ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> SET ( <replaceable class="parameter">storage_parameter</replaceable> = <replaceable class="parameter">value</replaceable> [, ... ] )
ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> RESET ( <replaceable class="parameter">storage_parameter</replaceable> [, ... ] )
ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> ALTER [ COLUMN ] <replaceable class="parameter">column_number</replaceable>
@@ -76,6 +78,30 @@ ALTER INDEX ALL IN TABLESPACE <replaceable class="parameter">name</replaceable>
</varlistentry>
<varlistentry>
+ <term><literal>ATTACH</literal></term>+ <listitem>+ <para>+ Causes the named index to become attached to the altered index.+ The named index must be on a partition of the table containing the+ index being altered, and have an equivalent definition. An attached+ index cannot be dropped by itself, and will automatically be dropped+ if its parent index is dropped.+ </para>+ </listitem>+ </varlistentry>++ <varlistentry>+ <term><literal>DETACH</literal></term>+ <listitem>+ <para>+ Causes the named index to become detached from the altered index,+ severing their link and turning them into separately-operable+ objects.+ </para>+ </listitem>+ </varlistentry>++ <varlistentry>
<term><literal>DEPENDS ON EXTENSION</literal></term>
<listitem>
<para>
diff --git a/doc/src/sgml/ref/create_index.sgml b/doc/src/sgml/ref/create_index.sgmlindex 92c0090dfd..6f923e61a2 100644--- a/doc/src/sgml/ref/create_index.sgml+++ b/doc/src/sgml/ref/create_index.sgml@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> ] ON <replaceable class="parameter">table_name</replaceable> [ USING <replaceable class="parameter">method</replaceable> ]+CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] <replaceable class="parameter">name</replaceable> ] ON [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ USING <replaceable class="parameter">method</replaceable> ]
( { <replaceable class="parameter">column_name</replaceable> | ( <replaceable class="parameter">expression</replaceable> ) } [ COLLATE <replaceable class="parameter">collation</replaceable> ] [ <replaceable class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] [, ...] )
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> = <replaceable class="parameter">value</replaceable> [, ... ] ) ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
@@ -152,6 +152,16 @@ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] <replaceable class=
</varlistentry>
<varlistentry>
+ <term><literal>ONLY</literal></term>+ <listitem>+ <para>+ Indicates not to recurse creating indexes on partitions, if the+ table is partitioned. The default is to recurse.+ </para>+ </listitem>+ </varlistentry>++ <varlistentry>
<term><replaceable class="parameter">table_name</replaceable></term>
<listitem>
<para>
@@ -546,6 +556,21 @@ Indexes:
</para>
<para>
+ When <literal>CREATE INDEX</literal> is invoked on a partitioned+ table, the default behavior is to recurse to all partitions to+ ensure they all have a matching indexes. Each partition is first+ checked to determine whether equivalent index already exists,+ and if so, that index will become attached as a partition index to+ the index being created, which will be its parent index. If no+ matching index exists, a new index will be created and automatically+ attached. If <literal>ONLY</literal> is specified, no recursion+ is done. Note, however, that any partition that is created in the+ future using <command>CREATE TABLE .. PARTITION OF</command> will+ automatically contain the index regardless of whether this option+ was specified.+ </para>++ <para>
For index methods that support ordered scans (currently, only B-tree),
the optional clauses <literal>ASC</literal>, <literal>DESC</literal>, <literal>NULLS
FIRST</literal>, and/or <literal>NULLS LAST</literal> can be specified to modify
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.cindex 3d0ce9af6f..c9cea08ace 100644--- a/src/backend/access/common/reloptions.c+++ b/src/backend/access/common/reloptions.c@@ -983,6 +983,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
options = view_reloptions(datum, false);
break;
case RELKIND_INDEX:
+ case RELKIND_PARTITIONED_INDEX:
options = index_reloptions(amoptions, datum, false);
break;
case RELKIND_FOREIGN_TABLE:
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.cindex 3acef279f4..b304944913 100644--- a/src/backend/access/heap/heapam.c+++ b/src/backend/access/heap/heapam.c@@ -1293,7 +1293,8 @@ heap_open(Oid relationId, LOCKMODE lockmode)
r = relation_open(relationId, lockmode);
- if (r->rd_rel->relkind == RELKIND_INDEX)+ if (r->rd_rel->relkind == RELKIND_INDEX ||+ r->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an index",
@@ -1321,7 +1322,8 @@ heap_openrv(const RangeVar *relation, LOCKMODE lockmode)
r = relation_openrv(relation, lockmode);
- if (r->rd_rel->relkind == RELKIND_INDEX)+ if (r->rd_rel->relkind == RELKIND_INDEX ||+ r->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an index",
@@ -1353,7 +1355,8 @@ heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
if (r)
{
- if (r->rd_rel->relkind == RELKIND_INDEX)+ if (r->rd_rel->relkind == RELKIND_INDEX ||+ r->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an index",
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.cindex edf4172eb2..c60db1eef5 100644--- a/src/backend/access/index/indexam.c+++ b/src/backend/access/index/indexam.c@@ -154,7 +154,8 @@ index_open(Oid relationId, LOCKMODE lockmode)
r = relation_open(relationId, lockmode);
- if (r->rd_rel->relkind != RELKIND_INDEX)+ if (r->rd_rel->relkind != RELKIND_INDEX &&+ r->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not an index",
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.yindex 2e1fef0350..95835ac1e7 100644--- a/src/backend/bootstrap/bootparse.y+++ b/src/backend/bootstrap/bootparse.y@@ -321,6 +321,7 @@ Boot_DeclareIndexStmt:
DefineIndex(relationId,
stmt,
$4,
+ InvalidOid,
false,
false,
false,
@@ -365,6 +366,7 @@ Boot_DeclareUniqueIndexStmt:
DefineIndex(relationId,
stmt,
$5,
+ InvalidOid,
false,
false,
false,
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.cindex ccde66a7dd..89e95ace37 100644--- a/src/backend/catalog/aclchk.c+++ b/src/backend/catalog/aclchk.c@@ -1766,7 +1766,8 @@ ExecGrant_Relation(InternalGrant *istmt)
pg_class_tuple = (Form_pg_class) GETSTRUCT(tuple);
/* Not sensible to grant on an index */
- if (pg_class_tuple->relkind == RELKIND_INDEX)+ if (pg_class_tuple->relkind == RELKIND_INDEX ||+ pg_class_tuple->relkind == RELKIND_PARTITIONED_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an index",
@@ -5347,7 +5348,8 @@ recordExtObjInitPriv(Oid objoid, Oid classoid)
pg_class_tuple = (Form_pg_class) GETSTRUCT(tuple);
/* Indexes don't have permissions */
- if (pg_class_tuple->relkind == RELKIND_INDEX)+ if (pg_class_tuple->relkind == RELKIND_INDEX ||+ pg_class_tuple->relkind == RELKIND_PARTITIONED_INDEX)
return;
/* Composite types don't have permissions either */
@@ -5632,7 +5634,8 @@ removeExtObjInitPriv(Oid objoid, Oid classoid)
pg_class_tuple = (Form_pg_class) GETSTRUCT(tuple);
/* Indexes don't have permissions */
- if (pg_class_tuple->relkind == RELKIND_INDEX)+ if (pg_class_tuple->relkind == RELKIND_INDEX ||+ pg_class_tuple->relkind == RELKIND_PARTITIONED_INDEX)
return;
/* Composite types don't have permissions either */
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.cindex 033c4358ea..720c6dd0bd 100644--- a/src/backend/catalog/dependency.c+++ b/src/backend/catalog/dependency.c@@ -1109,7 +1109,8 @@ doDeletion(const ObjectAddress *object, int flags)
{
char relKind = get_rel_relkind(object->objectId);
- if (relKind == RELKIND_INDEX)+ if (relKind == RELKIND_INDEX ||+ relKind == RELKIND_PARTITIONED_INDEX)
{
bool concurrent = ((flags & PERFORM_DELETION_CONCURRENTLY) != 0);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.cindex 9e14880b99..bc9fba9dfa 100644--- a/src/backend/catalog/heap.c+++ b/src/backend/catalog/heap.c@@ -294,6 +294,7 @@ heap_create(const char *relname,
case RELKIND_COMPOSITE_TYPE:
case RELKIND_FOREIGN_TABLE:
case RELKIND_PARTITIONED_TABLE:
+ case RELKIND_PARTITIONED_INDEX:
create_storage = false;
/*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.cindex 17faeffada..4a92c4d9d2 100644--- a/src/backend/catalog/index.c+++ b/src/backend/catalog/index.c@@ -41,6 +41,7 @@
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_constraint_fn.h"
+#include "catalog/pg_depend.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_tablespace.h"
@@ -98,6 +99,7 @@ static void InitializeAttributeOids(Relation indexRelation,
int numatts, Oid indexoid);
static void AppendAttributeTuples(Relation indexRelation, int numatts);
static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
+ Oid parentIndexId,
IndexInfo *indexInfo,
Oid *collationOids,
Oid *classOids,
@@ -551,6 +553,7 @@ AppendAttributeTuples(Relation indexRelation, int numatts)
static void
UpdateIndexRelation(Oid indexoid,
Oid heapoid,
+ Oid parentIndexOid,
IndexInfo *indexInfo,
Oid *collationOids,
Oid *classOids,
@@ -624,6 +627,7 @@ UpdateIndexRelation(Oid indexoid,
values[Anum_pg_index_indexrelid - 1] = ObjectIdGetDatum(indexoid);
values[Anum_pg_index_indrelid - 1] = ObjectIdGetDatum(heapoid);
+ values[Anum_pg_index_indparentidx - 1 ] = ObjectIdGetDatum(parentIndexOid);
values[Anum_pg_index_indnatts - 1] = Int16GetDatum(indexInfo->ii_NumIndexAttrs);
values[Anum_pg_index_indisunique - 1] = BoolGetDatum(indexInfo->ii_Unique);
values[Anum_pg_index_indisprimary - 1] = BoolGetDatum(primary);
@@ -670,6 +674,8 @@ UpdateIndexRelation(Oid indexoid,
* indexRelationId: normally, pass InvalidOid to let this routine
* generate an OID for the index. During bootstrap this may be
* nonzero to specify a preselected OID.
+ * parentIndexRelid: if creating an index partition, the OID of the+ * parent index; otherwise InvalidOid.
* relFileNode: normally, pass InvalidOid to get new storage. May be
* nonzero to attach an existing valid build.
* indexInfo: same info executor uses to insert into the index
@@ -695,6 +701,8 @@ UpdateIndexRelation(Oid indexoid,
* INDEX_CREATE_IF_NOT_EXISTS:
* do not throw an error if a relation with the same name
* already exists.
+ * INDEX_CREATE_PARTITIONED:+ * create a partitioned index (table must be partitioned)
* constr_flags: flags passed to index_constraint_create
* (only if INDEX_CREATE_ADD_CONSTRAINT is set)
* allow_system_table_mods: allow table to be a system catalog
@@ -706,6 +714,7 @@ Oid
index_create(Relation heapRelation,
const char *indexRelationName,
Oid indexRelationId,
+ Oid parentIndexRelid,
Oid relFileNode,
IndexInfo *indexInfo,
List *indexColNames,
@@ -732,11 +741,16 @@ index_create(Relation heapRelation,
char relpersistence;
bool isprimary = flags & INDEX_CREATE_IS_PRIMARY;
bool concurrent = flags & INDEX_CREATE_CONCURRENT;
+ bool partitioned = flags & INDEX_CREATE_PARTITIONED;+ char relkind;
/* constraint flags can only be set when constraint is requested */
Assert((constr_flags == 0) ||
((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0));
+ /* partitioned indexes must never be "built" by themselves */+ Assert(!partitioned || (flags & INDEX_CREATE_SKIP_BUILD));+ relkind = partitioned ? RELKIND_PARTITIONED_INDEX : RELKIND_INDEX;
is_exclusion = (indexInfo->ii_ExclusionOps != NULL);
pg_class = heap_open(RelationRelationId, RowExclusiveLock);
@@ -864,7 +878,7 @@ index_create(Relation heapRelation,
indexRelationId,
relFileNode,
indexTupDesc,
- RELKIND_INDEX,+ relkind,
relpersistence,
shared_relation,
mapped_relation,
@@ -921,7 +935,8 @@ index_create(Relation heapRelation,
* (Or, could define a rule to maintain the predicate) --Nels, Feb '92
* ----------------
*/
- UpdateIndexRelation(indexRelationId, heapRelationId, indexInfo,+ UpdateIndexRelation(indexRelationId, heapRelationId, parentIndexRelid,+ indexInfo,
collationObjectId, classObjectId, coloptions,
isprimary, is_exclusion,
(constr_flags & INDEX_CONSTR_CREATE_DEFERRABLE) == 0,
@@ -1010,6 +1025,17 @@ index_create(Relation heapRelation,
}
}
+ /* Store dependency on parent index, if any */+ if (OidIsValid(parentIndexRelid))+ {+ referenced.classId = RelationRelationId;+ referenced.objectId = parentIndexRelid;+ referenced.objectSubId = 0;++ recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);+ }++
/* Store dependency on collations */
/* The default collation is pinned, so don't bother recording it */
for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
@@ -1551,9 +1577,10 @@ index_drop(Oid indexId, bool concurrent)
}
/*
- * Schedule physical removal of the files+ * Schedule physical removal of the files (if any)
*/
- RelationDropStorage(userIndexRelation);+ if (userIndexRelation->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)+ RelationDropStorage(userIndexRelation);
/*
* Close and flush the index's relcache entry, to ensure relcache doesn't
@@ -1696,6 +1723,45 @@ BuildIndexInfo(Relation index)
return ii;
}
+/*+ * Compare two IndexInfos, and return true if they are similar enough that an+ * index built with one can pass as an index built with the other. If an+ * attmap is given, the indexes are from tables where the columns might be in+ * different physical locations, so use the map to match the column by name.+ */+bool+CompareIndexInfo(IndexInfo *info1, IndexInfo *info2, AttrNumber *attmap)+{+ int i;++ if (info1->ii_NumIndexAttrs != info2->ii_NumIndexAttrs)+ return false;++ for (i = 0; i < info1->ii_NumIndexAttrs; i++)+ {+ /* XXX use attmap here */+ if (info1->ii_KeyAttrNumbers[i] != info2->ii_KeyAttrNumbers[i])+ return false;+ }++ /* Expression indexes are currently not considered equal. Someday ... */+ if (info1->ii_Expressions != NIL || info2->ii_Expressions != NIL)+ return false;++ /* Can this be relaxed? */+ if (!equal(info1->ii_Predicate, info2->ii_Predicate))+ return false;++ /* Probably this can be relaxed someday */+ if (info1->ii_ExclusionOps != NULL || info2->ii_ExclusionOps != NULL)+ return false;++ if (info1->ii_Unique != info2->ii_Unique)+ return false;++ return true;+}+
/* ----------------
* BuildSpeculativeIndexInfo
* Add extra state to IndexInfo record
@@ -1918,6 +1984,9 @@ index_update_stats(Relation rel,
elog(ERROR, "could not find tuple for relation %u", relid);
rd_rel = (Form_pg_class) GETSTRUCT(tuple);
+ /* Should this be a more comprehensive test? */+ Assert(rd_rel->relkind != RELKIND_PARTITIONED_INDEX);+
/* Apply required updates, if any, to copied tuple */
dirty = false;
@@ -3328,6 +3397,14 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
iRel = index_open(indexId, AccessExclusiveLock);
/*
+ * The case of reindexing partitioned tables and indexes is handled+ * differently by upper layers, so this case shouldn't arise.+ */+ if (iRel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)+ elog(ERROR, "unsupported relation kind for index \"%s\"",+ RelationGetRelationName(iRel));++ /*
* Don't allow reindex on temp tables of other backends ... their local
* buffer manager is not going to cope.
*/
@@ -3526,6 +3603,22 @@ reindex_relation(Oid relid, int flags, int options)
*/
rel = heap_open(relid, ShareLock);
+ /*+ * This may be useful when implemented someday; but that day is not today.+ * For now, avoid erroring out when called in a multi-table context+ * (REINDEX SCHEMA) and happen to come across a partitioned table. The+ * partitions may be reindexed on their own anyway.+ */+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)+ {+ ereport(WARNING,+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),+ errmsg("REINDEX of partitioned tables is not yet implemented, skipping \"%s\"",+ RelationGetRelationName(rel))));+ heap_close(rel, ShareLock);+ return false;+ }+
toast_relid = rel->rd_rel->reltoastrelid;
/*
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.cindex 8d55c76fc4..0f7c7318f4 100644--- a/src/backend/catalog/objectaddress.c+++ b/src/backend/catalog/objectaddress.c@@ -1216,7 +1216,8 @@ get_relation_by_qualified_name(ObjectType objtype, List *object,
switch (objtype)
{
case OBJECT_INDEX:
- if (relation->rd_rel->relkind != RELKIND_INDEX)+ if (relation->rd_rel->relkind != RELKIND_INDEX &&+ relation->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not an index",
@@ -3476,6 +3477,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
relname);
break;
case RELKIND_INDEX:
+ case RELKIND_PARTITIONED_INDEX:
appendStringInfo(buffer, _("index %s"),
relname);
break;
@@ -3950,6 +3952,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
appendStringInfoString(buffer, "table");
break;
case RELKIND_INDEX:
+ case RELKIND_PARTITIONED_INDEX:
appendStringInfoString(buffer, "index");
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.cindex cf0086b9bd..53db972dec 100644--- a/src/backend/catalog/pg_depend.c+++ b/src/backend/catalog/pg_depend.c@@ -656,14 +656,19 @@ get_constraint_index(Oid constraintId)
/*
* We assume any internal dependency of an index on the constraint
- * must be what we are looking for. (The relkind test is just- * paranoia; there shouldn't be any such dependencies otherwise.)+ * must be what we are looking for.
*/
if (deprec->classid == RelationRelationId &&
deprec->objsubid == 0 &&
- deprec->deptype == DEPENDENCY_INTERNAL &&- get_rel_relkind(deprec->objid) == RELKIND_INDEX)+ deprec->deptype == DEPENDENCY_INTERNAL)
{
+ char relkind = get_rel_relkind(deprec->objid);++ /* This is pure paranoia; there shouldn't be any such */+ if (relkind != RELKIND_INDEX &&+ relkind != RELKIND_PARTITIONED_INDEX)+ break;+
indexId = deprec->objid;
break;
}
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.cindex 539ca79ad3..b1be2bee36 100644--- a/src/backend/catalog/toasting.c+++ b/src/backend/catalog/toasting.c@@ -328,6 +328,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
coloptions[1] = 0;
index_create(toast_rel, toast_idxname, toastIndexOid, InvalidOid,
+ InvalidOid,
indexInfo,
list_make2("chunk_id", "chunk_seq"),
BTREE_AM_OID,
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.cindex 963cfedcb4..c289f62b9d 100644--- a/src/backend/commands/indexcmds.c+++ b/src/backend/commands/indexcmds.c@@ -23,7 +23,9 @@
#include "catalog/catalog.h"
#include "catalog/index.h"
#include "catalog/indexing.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_tablespace.h"
@@ -35,6 +37,7 @@
#include "commands/tablespace.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/planner.h"
@@ -77,6 +80,7 @@ static char *ChooseIndexNameAddition(List *colnames);
static List *ChooseIndexColumnNames(List *indexElems);
static void RangeVarCallbackForReindexIndex(const RangeVar *relation,
Oid relId, Oid oldRelId, void *arg);
+static void ReindexPartitionedIndex(Relation parentIdx);
/*
* CheckIndexCompatible
@@ -292,14 +296,15 @@ CheckIndexCompatible(Oid oldId,
* 'stmt': IndexStmt describing the properties of the new index.
* 'indexRelationId': normally InvalidOid, but during bootstrap can be
* nonzero to specify a preselected OID for the index.
+ * 'parentIndexId': the OID of the parent index; InvalidOid if not the child+ * of a partitioned index.
* 'is_alter_table': this is due to an ALTER rather than a CREATE operation.
* 'check_rights': check for CREATE rights in namespace and tablespace. (This
* should be true except when ALTER is deleting/recreating an index.)
* 'check_not_in_use': check for table not already in use in current session.
* This should be true unless caller is holding the table open, in which
* case the caller had better have checked it earlier.
- * 'skip_build': make the catalog entries but leave the index file empty;- * it will be filled later.+ * 'skip_build': make the catalog entries but don't create the index files
* 'quiet': suppress the NOTICE chatter ordinarily provided for constraints.
*
* Returns the object address of the created index.
@@ -308,6 +313,7 @@ ObjectAddress
DefineIndex(Oid relationId,
IndexStmt *stmt,
Oid indexRelationId,
+ Oid parentIndexId,
bool is_alter_table,
bool check_rights,
bool check_not_in_use,
@@ -330,6 +336,7 @@ DefineIndex(Oid relationId,
IndexAmRoutine *amRoutine;
bool amcanorder;
amoptions_function amoptions;
+ bool partitioned;
Datum reloptions;
int16 *coloptions;
IndexInfo *indexInfo;
@@ -382,23 +389,56 @@ DefineIndex(Oid relationId,
{
case RELKIND_RELATION:
case RELKIND_MATVIEW:
+ case RELKIND_PARTITIONED_TABLE:
/* OK */
break;
case RELKIND_FOREIGN_TABLE:
+ /*+ * Custom error message for FOREIGN TABLE since the term is close+ * to a regular table and can confuse the user.+ */
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot create index on foreign table \"%s\"",
RelationGetRelationName(rel))));
- case RELKIND_PARTITIONED_TABLE:- ereport(ERROR,- (errcode(ERRCODE_WRONG_OBJECT_TYPE),- errmsg("cannot create index on partitioned table \"%s\"",- RelationGetRelationName(rel))));
default:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a table or materialized view",
RelationGetRelationName(rel))));
+ break;+ }++ /*+ * Establish behavior for partitioned tables, and verify sanity of+ * parameters.+ *+ * We do not build an actual index in this case; we only create a few+ * catalog entries. The actual indexes are built by recursing for each+ * partition.+ */+ partitioned = rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE;+ if (partitioned)+ {+ if (stmt->concurrent)+ ereport(ERROR,+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),+ errmsg("cannot create index on partitioned table \"%s\" concurrently",+ RelationGetRelationName(rel))));+ if (stmt->unique)+ ereport(ERROR,+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),+ errmsg("cannot create unique index on partitioned table \"%s\"",+ RelationGetRelationName(rel))));+ if (stmt->excludeOpNames)+ ereport(ERROR,+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),+ errmsg("cannot create exclusion constraints on partitioned table \"%s\"",+ RelationGetRelationName(rel))));+ if (is_alter_table)+ ereport(ERROR,+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),+ errmsg("cannot create constraints on partitioned tables")));
}
/*
@@ -665,17 +705,20 @@ DefineIndex(Oid relationId,
/*
* Make the catalog entries for the index, including constraints. This
* step also actually builds the index, except if caller requested not to
- * or in concurrent mode, in which case it'll be done later.+ * or in concurrent mode, in which case it'll be done later, or+ * doing a partitioned index (because those don't have storage).
*/
flags = constr_flags = 0;
if (stmt->isconstraint)
flags |= INDEX_CREATE_ADD_CONSTRAINT;
- if (skip_build || stmt->concurrent)+ if (skip_build || stmt->concurrent || partitioned)
flags |= INDEX_CREATE_SKIP_BUILD;
if (stmt->if_not_exists)
flags |= INDEX_CREATE_IF_NOT_EXISTS;
if (stmt->concurrent)
flags |= INDEX_CREATE_CONCURRENT;
+ if (partitioned)+ flags |= INDEX_CREATE_PARTITIONED;
if (stmt->primary)
flags |= INDEX_CREATE_IS_PRIMARY;
@@ -685,8 +728,8 @@ DefineIndex(Oid relationId,
constr_flags |= INDEX_CONSTR_CREATE_INIT_DEFERRED;
indexRelationId =
- index_create(rel, indexRelationName, indexRelationId, stmt->oldNode,- indexInfo, indexColNames,+ index_create(rel, indexRelationName, indexRelationId, parentIndexId,+ stmt->oldNode, indexInfo, indexColNames,
accessMethodId, tablespaceId,
collationObjectId, classObjectId,
coloptions, reloptions,
@@ -706,6 +749,145 @@ DefineIndex(Oid relationId,
CreateComments(indexRelationId, RelationRelationId, 0,
stmt->idxcomment);
+ if (partitioned)+ {+ /*+ * If ONLY was specified, we're done. Otherwise, recurse to each+ * partition to create the corresponding index, or flag an existing+ * index as a children of this one.+ *+ * If we're invoked internally, recurse always.+ */+ if ((stmt->relation && stmt->relation->inh) || !stmt->relation)+ {+ PartitionDesc partdesc = RelationGetPartitionDesc(rel);+ Oid *part_oids;+ int nparts;+ MemoryContext oldcxt;+ TupleDesc parentDesc;++ /*+ * XXX In this block, we're going to call DefineIndex recursively+ * on the partitions; when building CONCURRENTLY, this will close+ * and open one transaction each time. If indexes exist on some+ * partitions, we will not use separate transactions for those.+ * That seems okay, since the changes done in that case will be+ * catalog only and should not take long.+ *+ * XXX Note that CONCURRENTLY is not implemented yet for+ * partitioned tables, though; make sure to fix any holes there+ * before enabling that ...+ */++ /*+ * For the concurrent case, we must ensure not to lose the array+ * of partitions we're going to work on, so copy it out of relcache.+ * PortalContext has the lifetime we need. Also, save a copy of+ * the relation's tuple desc.+ */+ oldcxt = MemoryContextSwitchTo(PortalContext);++ nparts = partdesc->nparts;+ part_oids = palloc(sizeof(Oid) * nparts);+ memcpy(part_oids, partdesc->oids, sizeof(Oid) * nparts);++ parentDesc = palloc(TupleDescSize(RelationGetDescr(rel)));+ TupleDescCopy(parentDesc, RelationGetDescr(rel));++ MemoryContextSwitchTo(oldcxt);++ heap_close(rel, NoLock);++ /*+ * Now recurse, one child at a time. Note that if any child is in+ * turn a partitioned table, this will recursively do the right thing.+ * In the concurrent case, this will close the current transaction and+ * open a new one.+ *+ * XXX Verify this coding carefully; see also ATExecAttachPartition+ */+ for (i = 0; i < nparts; i++)+ {+ Oid childRelid = part_oids[i];+ Relation childrel;+ List *childidxs;+ ListCell *cell;+ AttrNumber *attmap = NULL;+ bool found = false;++ childrel = heap_open(childRelid, lockmode);+ /* childidxs goes away with transaction in concurrent mode;+ * we leak it otherwise */+ childidxs = RelationGetIndexList(childrel);++ foreach(cell, childidxs)+ {+ Oid cldidxid = lfirst_oid(cell);+ Relation cldidx;+ IndexInfo *cldIdxInfo;++ cldidx = index_open(cldidxid, AccessShareLock);++ /* this index is already partition of another one */+ if (cldidx->rd_index->indparentidx != 0)+ {+ index_close(cldidx, AccessShareLock);+ continue;+ }++ cldIdxInfo = BuildIndexInfo(cldidx);+ if (attmap == NULL)+ attmap =+ convert_tuples_by_name_map(RelationGetDescr(childrel),+ parentDesc,+ gettext_noop("could not convert row type"));++ if (CompareIndexInfo(cldIdxInfo, indexInfo, attmap))+ {+ /* matched */+ indexSetParentIndex(cldidx, indexRelationId);+ found = true;+ index_close(cldidx, AccessShareLock);+ break;+ }++ index_close(cldidx, AccessShareLock);+ }++ heap_close(childrel, NoLock);++ if (!found)+ {+ IndexStmt *childStmt;++ childStmt = copyObject(stmt);+ childStmt->idxname = NULL;+ childStmt->relationId = childRelid;++ /*+ * Note that the event trigger command stash will only contain+ * a command to create the top-level index, not each of the+ * individual index partitions.+ */+ /* XXX emit a DDL event here? */+ DefineIndex(childRelid, childStmt,+ InvalidOid, /* no predefined OID */+ indexRelationId, /* this is our child */+ false, check_rights, check_not_in_use,+ false, quiet);+ }+ }+ }+ else+ heap_close(rel, NoLock);++ /*+ * Indexes on partitioned tables are not themselves built, so we're+ * done here.+ */+ return address;+ }+
if (!stmt->concurrent)
{
/* Close the heap and we're done, in the non-concurrent case */
@@ -1762,7 +1944,7 @@ ChooseIndexColumnNames(List *indexElems)
* ReindexIndex
* Recreate a specific index.
*/
-Oid+void
ReindexIndex(RangeVar *indexRelation, int options)
{
Oid indOid;
@@ -1785,12 +1967,17 @@ ReindexIndex(RangeVar *indexRelation, int options)
* lock on the index.
*/
irel = index_open(indOid, NoLock);
++ if (irel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)+ {+ ReindexPartitionedIndex(irel);+ return;+ }+
persistence = irel->rd_rel->relpersistence;
index_close(irel, NoLock);
reindex_index(indOid, false, persistence, options);
-- return indOid;
}
/*
@@ -1829,7 +2016,8 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation,
relkind = get_rel_relkind(relId);
if (!relkind)
return;
- if (relkind != RELKIND_INDEX)+ if (relkind != RELKIND_INDEX &&+ relkind != RELKIND_PARTITIONED_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not an index", relation->relname)));
@@ -1973,6 +2161,12 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
/*
* Only regular tables and matviews can have indexes, so ignore any
* other kind of relation.
+ *+ * XXX it is tempting to also consider partitioned tables here, but+ * that has the problem that if the children are in the same schema,+ * they would be processed twice. Maybe we could have a separate+ * list of partitioned tables, and expand that afterwards into relids,+ * ignoring any duplicates.
*/
if (classtuple->relkind != RELKIND_RELATION &&
classtuple->relkind != RELKIND_MATVIEW)
@@ -2035,3 +2229,43 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
MemoryContextDelete(private_context);
}
++/*+ * Reindex each child of a partitioned index.+ *+ * The parent index is given, locked in AccessExclusive mode; this routine+ * obtains the list of children and releases the lock on parent before+ * applying reindex on each child.+ */+static void+ReindexPartitionedIndex(Relation parentIdx)+{+ ereport(ERROR,+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),+ errmsg("REINDEX is not yet implemented for partitioned indexes")));+}++/*+ * Update the pg_index tuple corresponding to the given index on a partition+ * to indicate that the given index OID is now its parent partitioned index.+ */+void+indexSetParentIndex(Relation idx, Oid parentOid)+{+ Relation pgindex;+ HeapTuple indTup;+ Form_pg_index indForm;++ /* Make sure this is an index */+ Assert(idx->rd_rel->relkind == RELKIND_INDEX ||+ idx->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);++ pgindex = heap_open(IndexRelationId, RowExclusiveLock);+ indTup = idx->rd_indextuple;+ indForm = (Form_pg_index) GETSTRUCT(indTup);+ indForm->indparentidx = parentOid;++ CatalogTupleUpdate(pgindex, &(indTup->t_self), indTup);++ heap_close(pgindex, RowExclusiveLock);+}diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.cindex a723d98666..6fc71c9674 100644--- a/src/backend/commands/tablecmds.c+++ b/src/backend/commands/tablecmds.c@@ -266,6 +266,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
gettext_noop("table \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not a table"),
gettext_noop("Use DROP TABLE to remove a table.")},
+ {RELKIND_PARTITIONED_INDEX,+ ERRCODE_UNDEFINED_OBJECT,+ gettext_noop("index \"%s\" does not exist"),+ gettext_noop("index \"%s\" does not exist, skipping"),+ gettext_noop("\"%s\" is not an index"),+ gettext_noop("Use DROP INDEX to remove an index.")},
{'\0', 0, NULL, NULL, NULL, NULL}
};
@@ -481,6 +487,9 @@ static void ValidatePartitionConstraints(List **wqueue, Relation scanrel,
List *partConstraint,
bool validate_default);
static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
+static ObjectAddress ATExecAttachPartitionIdx(List **wqueue, Relation rel,+ RangeVar *name);+static ObjectAddress ATExecDetachPartitionIdx(Relation rel, RangeVar *name);
/* ----------------------------------------------------------------
@@ -901,6 +910,52 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
}
/*
+ * Now that the partition spec is installed, we can create any indexes+ * from the partitioned table into the partitions. We can't do it+ * earlier, because if this is a partition which in turn is partitioned,+ * DefineIndex would not be able to recurse correctly into children to+ * apply indexes from the parent table.+ */+ if (stmt->partbound)+ {+ Oid parentId = linitial_oid(inheritOids);+ Relation parent;+ List *idxlist;+ ListCell *cell;++ /* Already have strong enough lock on the parent */+ parent = heap_open(parentId, NoLock);+ idxlist = RelationGetIndexList(parent);++ /*+ * For each index in the parent table, create one in the partition+ */+ foreach(cell, idxlist)+ {+ Relation idxRel = index_open(lfirst_oid(cell), AccessShareLock);+ AttrNumber *attmap;+ IndexStmt *idxstmt;++ attmap = convert_tuples_by_name_map(RelationGetDescr(rel),+ RelationGetDescr(parent),+ gettext_noop("could not convert row type"));+ idxstmt =+ generateClonedIndexStmt(NULL, RelationGetRelid(rel), idxRel,+ attmap, RelationGetDescr(rel)->natts);+ DefineIndex(RelationGetRelid(rel),+ idxstmt,+ InvalidOid,+ RelationGetRelid(idxRel),+ false, false, false, false, false);++ index_close(idxRel, AccessShareLock);+ }++ list_free(idxlist);+ heap_close(parent, NoLock);+ }++ /*
* Now add any newly specified column default values and CHECK constraints
* to the new relation. These are passed to us in the form of raw
* parsetrees; we need to transform them to executable expression trees
@@ -1180,10 +1235,13 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
* but RemoveRelations() can only pass one relkind for a given relation.
* It chooses RELKIND_RELATION for both regular and partitioned tables.
* That means we must be careful before giving the wrong type error when
- * the relation is RELKIND_PARTITIONED_TABLE.+ * the relation is RELKIND_PARTITIONED_TABLE. There's an equivalent+ * problem with indexes.
*/
if (classform->relkind == RELKIND_PARTITIONED_TABLE)
expected_relkind = RELKIND_RELATION;
+ else if (classform->relkind == RELKIND_PARTITIONED_INDEX)+ expected_relkind = RELKIND_INDEX;
else
expected_relkind = classform->relkind;
@@ -1211,7 +1269,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
* we do it the other way around. No error if we don't find a pg_index
* entry, though --- the relation may have been dropped.
*/
- if (relkind == RELKIND_INDEX && relOid != oldRelOid)+ if ((relkind == RELKIND_INDEX || relkind == RELKIND_PARTITIONED_INDEX) &&+ relOid != oldRelOid)
{
state->heapOid = IndexGetRelation(relOid, true);
if (OidIsValid(state->heapOid))
@@ -2541,6 +2600,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing)
relkind != RELKIND_MATVIEW &&
relkind != RELKIND_COMPOSITE_TYPE &&
relkind != RELKIND_INDEX &&
+ relkind != RELKIND_PARTITIONED_INDEX &&
relkind != RELKIND_FOREIGN_TABLE &&
relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
@@ -3020,7 +3080,8 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal)
/*
* Also rename the associated constraint, if any.
*/
- if (targetrelation->rd_rel->relkind == RELKIND_INDEX)+ if (targetrelation->rd_rel->relkind == RELKIND_INDEX ||+ targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
{
Oid constraintId = get_index_constraint(myrelid);
@@ -3074,6 +3135,7 @@ CheckTableNotInUse(Relation rel, const char *stmt)
stmt, RelationGetRelationName(rel))));
if (rel->rd_rel->relkind != RELKIND_INDEX &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
AfterTriggerPendingOnRel(RelationGetRelid(rel)))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_IN_USE),
@@ -3766,7 +3828,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
break;
case AT_AttachPartition:
case AT_DetachPartition:
- ATSimplePermissions(rel, ATT_TABLE);+ ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX);
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
@@ -4113,10 +4175,18 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
ATExecGenericOptions(rel, (List *) cmd->def);
break;
case AT_AttachPartition:
- ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)+ ATExecAttachPartition(wqueue, rel, (PartitionCmd *) cmd->def);+ else+ ATExecAttachPartitionIdx(wqueue, rel,+ ((PartitionCmd *) cmd->def)->name);
break;
case AT_DetachPartition:
- ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)+ ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);+ else+ ATExecDetachPartitionIdx(rel,+ ((PartitionCmd *) cmd->def)->name);
break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
@@ -4751,6 +4821,7 @@ ATSimplePermissions(Relation rel, int allowed_targets)
actual_target = ATT_MATVIEW;
break;
case RELKIND_INDEX:
+ case RELKIND_PARTITIONED_INDEX:
actual_target = ATT_INDEX;
break;
case RELKIND_COMPOSITE_TYPE:
@@ -6195,6 +6266,7 @@ ATPrepSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newVa
if (rel->rd_rel->relkind != RELKIND_RELATION &&
rel->rd_rel->relkind != RELKIND_MATVIEW &&
rel->rd_rel->relkind != RELKIND_INDEX &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
@@ -6206,7 +6278,9 @@ ATPrepSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newVa
* We allow referencing columns by numbers only for indexes, since table
* column numbers could contain gaps if columns are later dropped.
*/
- if (rel->rd_rel->relkind != RELKIND_INDEX && !colName)+ if (rel->rd_rel->relkind != RELKIND_INDEX &&+ rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX &&+ !colName)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot refer to non-index column by number")));
@@ -6284,7 +6358,8 @@ ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newVa
errmsg("cannot alter system column \"%s\"",
colName)));
- if (rel->rd_rel->relkind == RELKIND_INDEX &&+ if ((rel->rd_rel->relkind == RELKIND_INDEX ||+ rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) &&
rel->rd_index->indkey.values[attnum - 1] != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -6797,6 +6872,7 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
address = DefineIndex(RelationGetRelid(rel),
stmt,
InvalidOid, /* no predefined OID */
+ InvalidOid, /* no parent index */
true, /* is_alter_table */
check_rights,
false, /* check_not_in_use - we did it already */
@@ -9198,7 +9274,9 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
{
char relKind = get_rel_relkind(foundObject.objectId);
- if (relKind == RELKIND_INDEX)+ /* XXX does this do the right thing? Probably not */+ if (relKind == RELKIND_INDEX ||+ relKind == RELKIND_PARTITIONED_INDEX)
{
Assert(foundObject.objectSubId == 0);
if (!list_member_oid(tab->changedIndexOids, foundObject.objectId))
@@ -10162,6 +10240,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
*/
if (tuple_class->relkind != RELKIND_COMPOSITE_TYPE &&
tuple_class->relkind != RELKIND_INDEX &&
+ tuple_class->relkind != RELKIND_PARTITIONED_INDEX &&
tuple_class->relkind != RELKIND_TOASTVALUE)
changeDependencyOnOwner(RelationRelationId, relationOid,
newOwnerId);
@@ -10169,7 +10248,8 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
/*
* Also change the ownership of the table's row type, if it has one
*/
- if (tuple_class->relkind != RELKIND_INDEX)+ if (tuple_class->relkind != RELKIND_INDEX &&+ tuple_class->relkind != RELKIND_PARTITIONED_INDEX)
AlterTypeOwnerInternal(tuple_class->reltype, newOwnerId);
/*
@@ -10194,6 +10274,15 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
list_free(index_oid_list);
}
+#if 0+ /* For partitioned indexes, recurse to each child index */+ if (tuple_class->relkind == RELKIND_PARTITIONED_INDEX)+ {+ foreach (i, child_index_oid)+ ATExecChangeOwner(lfirst_oid(i), newOwnerId, true, lockmode);+ }+#endif+
if (tuple_class->relkind == RELKIND_RELATION ||
tuple_class->relkind == RELKIND_MATVIEW)
{
@@ -10486,6 +10575,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
(void) view_reloptions(newOptions, true);
break;
case RELKIND_INDEX:
+ case RELKIND_PARTITIONED_INDEX:
(void) index_reloptions(rel->rd_amroutine->amoptions, newOptions, true);
break;
default:
@@ -10898,7 +10988,8 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt)
relForm->relkind != RELKIND_RELATION &&
relForm->relkind != RELKIND_PARTITIONED_TABLE) ||
(stmt->objtype == OBJECT_INDEX &&
- relForm->relkind != RELKIND_INDEX) ||+ relForm->relkind != RELKIND_INDEX &&+ relForm->relkind != RELKIND_PARTITIONED_INDEX) ||
(stmt->objtype == OBJECT_MATVIEW &&
relForm->relkind != RELKIND_MATVIEW))
continue;
@@ -13285,7 +13376,8 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not a composite type", rv->relname)));
- if (reltype == OBJECT_INDEX && relkind != RELKIND_INDEX+ if (reltype == OBJECT_INDEX && relkind != RELKIND_INDEX &&+ relkind != RELKIND_PARTITIONED_INDEX
&& !IsA(stmt, RenameStmt))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -14006,6 +14098,103 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
StorePartitionBound(attachrel, rel, cmd->bound);
/*
+ * Ensure a correct set of indexes in the partition. This either creates+ * a new index in the table being attached, or re-parents an existing one.+ */+ {+ AttrNumber *attmap = NULL;+ List *idxes;+ List *attachRelIdxs;+ Relation *attachrelIdxRels;+ IndexInfo **attachInfos;+ int i;+ ListCell *cell;++ idxes = RelationGetIndexList(rel);+ attachRelIdxs = RelationGetIndexList(attachrel);+ attachrelIdxRels = palloc(sizeof(Relation) * list_length(attachRelIdxs));+ attachInfos = palloc(sizeof(IndexInfo *) * list_length(attachRelIdxs));++ i = 0;+ foreach(cell, attachRelIdxs)+ {+ Oid cldIdxId = lfirst_oid(cell);++ attachrelIdxRels[i] = index_open(cldIdxId, AccessShareLock);+ attachInfos[i] = BuildIndexInfo(attachrelIdxRels[i]);+ i++;+ }++ /*+ * For each index on the partitioned table, find a match in the table+ * being attached as partition; if one is not found, create one.+ */+ foreach(cell, idxes)+ {+ Oid idx = lfirst_oid(cell);+ Relation idxRel = index_open(idx, AccessShareLock);+ IndexInfo *info;+ bool found = false;++ if (idxRel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)+ {+ index_close(idxRel, AccessShareLock);+ continue;+ }+ info = BuildIndexInfo(idxRel);+ if (attmap == NULL)+ attmap =+ convert_tuples_by_name_map(RelationGetDescr(attachrel),+ RelationGetDescr(rel),+ gettext_noop("could not convert row type"));++ for (i = 0; i < list_length(attachRelIdxs); i++)+ {+ /* already used it */+ if (attachrelIdxRels[i]->rd_index->indparentidx != 0)+ continue;++ if (CompareIndexInfo(info, attachInfos[i], attmap))+ {+ /* bingo. */+ indexSetParentIndex(attachrelIdxRels[i], idx);+ found = true;+ break;+ }+ }+ if (!found)+ {+ IndexStmt *stmt;++ stmt = generateClonedIndexStmt(NULL, RelationGetRelid(attachrel),+ idxRel, attmap,+ RelationGetDescr(rel)->natts);+ /* XXX emit a DDL event here */+ DefineIndex(RelationGetRelid(attachrel), stmt, InvalidOid,+ RelationGetRelid(idxRel),+ false, false, false, false, false);+ }++ index_close(idxRel, AccessShareLock);+ }++ /* Clean up. */+ if (attmap)+ pfree(attmap);++ for (i = 0; i < list_length(attachRelIdxs); i++)+ {+ pfree(attachInfos[i]);+ index_close(attachrelIdxRels[i], AccessShareLock);+ }++ if (idxes)+ pfree(idxes);+ if (attachRelIdxs)+ pfree(attachRelIdxs);+ }++ /*
* Generate partition constraint from the partition bound specification.
* If the parent itself is a partition, make sure to include its
* constraint as well.
@@ -14163,3 +14352,143 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
return address;
}
++/*+ * ALTER INDEX i1 ATTACH PARTITION i2+ */+static ObjectAddress+ATExecAttachPartitionIdx(List **wqueue, Relation rel, RangeVar *name)+{+ Relation partRel;+ ObjectAddress address;++ /* XXX do we need this strong a lock? */+ partRel = relation_openrv(name, AccessExclusiveLock);++ /* Ensure it's what we need */+ if (partRel->rd_rel->relkind != RELKIND_INDEX &&+ partRel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)+ ereport(ERROR,+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),+ errmsg("\"%s\" is not an index", RelationGetRelationName(partRel))));++ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));++ /* Silently do nothing if already the right state */+ if (partRel->rd_index->indparentidx != RelationGetRelid(rel))+ {+ ObjectAddress parentAddr;+ Relation parentTable,+ partTable;+ IndexInfo *childInfo;+ IndexInfo *parentInfo;+ AttrNumber *attmap;+ bool found;+ int i;+ PartitionDesc partDesc;++ if (OidIsValid(partRel->rd_index->indparentidx))+ ereport(ERROR,+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),+ errmsg("index \"%s\" is already a partition of another index",+ RelationGetRelationName(partRel))));++ /* Make sure it indexes a partition of the other index's table */+ /* XXX need to avoid deadlocks here somehow */+ parentTable = heap_open(rel->rd_index->indrelid, AccessShareLock);+ partTable = heap_open(partRel->rd_index->indrelid, AccessShareLock);++ partDesc = RelationGetPartitionDesc(parentTable);+ found = false;+ for (i = 0; i < partDesc->nparts; i++)+ {+ if (partDesc->oids[i] == RelationGetRelid(partTable))+ {+ found = true;+ break;+ }+ }+ if (!found)+ ereport(ERROR,+ (errmsg("index \"%s\" is not on a partition of table \"%s\"",+ RelationGetRelationName(partRel),+ RelationGetRelationName(parentTable))));++ /*+ * Make sure the indexes are compatible. It seems like this could be+ * relaxed a bit: for instance maybe it'd be useful to have a hash index+ * on one partition and a btree index in another. But let's keep this+ * simple for now.+ */+ childInfo = BuildIndexInfo(partRel);+ parentInfo = BuildIndexInfo(rel);+ /* XXX are these inverted? */+ attmap = convert_tuples_by_name_map(RelationGetDescr(partTable),+ RelationGetDescr(parentTable),+ gettext_noop("could not convert row type"));+ if (!CompareIndexInfo(parentInfo, childInfo, attmap))+ ereport(ERROR,+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),+ errmsg("definition of index \"%s\" is not compatible with index \"%s\"",+ RelationGetRelationName(partRel),+ RelationGetRelationName(rel))));++ /* All good -- do it */+ indexSetParentIndex(partRel, RelationGetRelid(rel));++ /* set up pg_depend */+ ObjectAddressSet(parentAddr, RelationRelationId, RelationGetRelid(rel));+ recordDependencyOn(&address, &parentAddr, DEPENDENCY_INTERNAL);++ relation_close(parentTable, AccessShareLock);+ relation_close(partTable, AccessShareLock);+ }++ relation_close(partRel, AccessExclusiveLock);++ return address;+}++/*+ * ALTER INDEX i1 DETACH INDEX i2+ */+static ObjectAddress+ATExecDetachPartitionIdx(Relation rel, RangeVar *name)+{+ Relation partRel;+ ObjectAddress address;++ /* XXX do we need this strong a lock? */+ partRel = relation_openrv(name, AccessExclusiveLock);++ /* Ensure it's what we need */+ if (partRel->rd_rel->relkind != RELKIND_INDEX &&+ partRel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)+ ereport(ERROR,+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),+ errmsg("\"%s\" is not an index", RelationGetRelationName(partRel))));++ /* Silently do nothing if already the right state */+ if (OidIsValid(partRel->rd_index->indparentidx))+ {+ if (partRel->rd_index->indparentidx != RelationGetRelid(rel))+ ereport(ERROR,+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),+ errmsg("\"%s\" is not attached to \"%s\"",+ RelationGetRelationName(partRel),+ RelationGetRelationName(rel))));++ /* all good */+ indexSetParentIndex(partRel, InvalidOid);++ deleteDependencyRecordsForClass(RelationRelationId,+ RelationGetRelid(partRel),+ RelationRelationId,+ DEPENDENCY_INTERNAL);+ }++ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partRel));+ relation_close(partRel, AccessExclusiveLock);++ return address;+}diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.yindex 78cbf8029c..2608396cae 100644--- a/src/backend/parser/gram.y+++ b/src/backend/parser/gram.y@@ -290,7 +290,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <ival> add_drop opt_asc_desc opt_nulls_order
%type <node> alter_table_cmd alter_type_cmd opt_collate_clause
- replica_identity partition_cmd+ replica_identity partition_cmd index_partition_cmd
%type <list> alter_table_cmds alter_type_cmds
%type <list> alter_identity_column_option_list
%type <defelt> alter_identity_column_option
@@ -1876,6 +1876,15 @@ AlterTableStmt:
n->missing_ok = true;
$$ = (Node *)n;
}
+ | ALTER INDEX qualified_name index_partition_cmd+ {+ AlterTableStmt *n = makeNode(AlterTableStmt);+ n->relation = $3;+ n->cmds = list_make1($4);+ n->relkind = OBJECT_INDEX;+ n->missing_ok = false;+ $$ = (Node *)n;+ }
| ALTER INDEX ALL IN_P TABLESPACE name SET TABLESPACE name opt_nowait
{
AlterTableMoveAllStmt *n =
@@ -2010,6 +2019,35 @@ partition_cmd:
}
;
+index_partition_cmd:+ /* ALTER INDEX <name> ATTACH PARTITION <index_name> */+ ATTACH PARTITION qualified_name+ {+ AlterTableCmd *n = makeNode(AlterTableCmd);+ PartitionCmd *cmd = makeNode(PartitionCmd);++ n->subtype = AT_AttachPartition;+ cmd->name = $3;+ cmd->bound = NULL;+ n->def = (Node *) cmd;++ $$ = (Node *) n;+ }+ /* ALTER INDEX <name> DETACH PARTITION <index_name> */+ | DETACH PARTITION qualified_name+ {+ AlterTableCmd *n = makeNode(AlterTableCmd);+ PartitionCmd *cmd = makeNode(PartitionCmd);++ n->subtype = AT_DetachPartition;+ cmd->name = $3;+ cmd->bound = NULL;+ n->def = (Node *) cmd;++ $$ = (Node *) n;+ }+ ;+
alter_table_cmd:
/* ALTER TABLE <name> ADD <coldef> */
ADD_P columnDef
@@ -7228,7 +7266,7 @@ defacl_privilege_target:
*****************************************************************************/
IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
- ON qualified_name access_method_clause '(' index_params ')'+ ON relation_expr access_method_clause '(' index_params ')'
opt_reloptions OptTableSpace where_clause
{
IndexStmt *n = makeNode(IndexStmt);
@@ -7255,7 +7293,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
$$ = (Node *)n;
}
| CREATE opt_unique INDEX opt_concurrently IF_P NOT EXISTS index_name
- ON qualified_name access_method_clause '(' index_params ')'+ ON relation_expr access_method_clause '(' index_params ')'
opt_reloptions OptTableSpace where_clause
{
IndexStmt *n = makeNode(IndexStmt);
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.cindex f9e8f6da0d..938e73f8ad 100644--- a/src/backend/parser/parse_utilcmd.c+++ b/src/backend/parser/parse_utilcmd.c@@ -3279,18 +3279,39 @@ transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd)
{
Relation parentRel = cxt->rel;
- /* the table must be partitioned */- if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)- ereport(ERROR,- (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),- errmsg("\"%s\" is not partitioned",- RelationGetRelationName(parentRel))));-- /* transform the partition bound, if any */- Assert(RelationGetPartitionKey(parentRel) != NULL);- if (cmd->bound != NULL)- cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,- cmd->bound);+ switch (parentRel->rd_rel->relkind)+ {+ case RELKIND_PARTITIONED_TABLE:+ /* transform the partition bound, if any */+ Assert(RelationGetPartitionKey(parentRel) != NULL);+ if (cmd->bound != NULL)+ cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,+ cmd->bound);+ break;+ case RELKIND_PARTITIONED_INDEX:+ /* nothing to check */+ Assert(cmd->bound == NULL);+ break;+ case RELKIND_RELATION:+ /* the table must be partitioned */+ ereport(ERROR,+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),+ errmsg("table \"%s\" is not partitioned",+ RelationGetRelationName(parentRel))));+ break;+ case RELKIND_INDEX:+ /* the index must be partitioned */+ ereport(ERROR,+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),+ errmsg("index \"%s\" is not partitioned",+ RelationGetRelationName(parentRel))));+ break;+ default:+ /* parser shouldn't let this case through */+ elog(ERROR, "\"%s\" is not a partitioned table or index",+ RelationGetRelationName(parentRel));+ break;+ }
}
/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.cindex 82a707af7b..93c09f73a8 100644--- a/src/backend/tcop/utility.c+++ b/src/backend/tcop/utility.c@@ -23,6 +23,7 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/namespace.h"
+#include "catalog/pg_inherits_fn.h"
#include "catalog/toasting.h"
#include "commands/alter.h"
#include "commands/async.h"
@@ -1296,6 +1297,7 @@ ProcessUtilitySlow(ParseState *pstate,
IndexStmt *stmt = (IndexStmt *) parsetree;
Oid relid;
LOCKMODE lockmode;
+ List *inheritors = NIL;
if (stmt->concurrent)
PreventTransactionChain(isTopLevel,
@@ -1317,6 +1319,9 @@ ProcessUtilitySlow(ParseState *pstate,
false, false,
RangeVarCallbackOwnsRelation,
NULL);
+ /* Also, lock any descendant tables if recursive */+ if (stmt->relation->inh)+ inheritors = find_all_inheritors(relid, lockmode, NULL);
/* Run parse analysis ... */
stmt = transformIndexStmt(relid, stmt, queryString);
@@ -1327,6 +1332,7 @@ ProcessUtilitySlow(ParseState *pstate,
DefineIndex(relid, /* OID of heap relation */
stmt,
InvalidOid, /* no predefined OID */
+ InvalidOid, /* no parent index */
false, /* is_alter_table */
true, /* check_rights */
true, /* check_not_in_use */
@@ -1342,6 +1348,9 @@ ProcessUtilitySlow(ParseState *pstate,
parsetree);
commandCollected = true;
EventTriggerAlterTableEnd();
++ if (inheritors)+ list_free(inheritors);
}
break;
diff --git a/src/backend/utils/adt/amutils.c b/src/backend/utils/adt/amutils.cindex f53b251b30..6af53402ce 100644--- a/src/backend/utils/adt/amutils.c+++ b/src/backend/utils/adt/amutils.c@@ -183,7 +183,8 @@ indexam_property(FunctionCallInfo fcinfo,
if (!HeapTupleIsValid(tuple))
PG_RETURN_NULL();
rd_rel = (Form_pg_class) GETSTRUCT(tuple);
- if (rd_rel->relkind != RELKIND_INDEX)+ if (rd_rel->relkind != RELKIND_INDEX &&+ rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
{
ReleaseSysCache(tuple);
PG_RETURN_NULL();
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.cindex 06cf32f5d7..0aaeb0f884 100644--- a/src/backend/utils/adt/ruleutils.c+++ b/src/backend/utils/adt/ruleutils.c@@ -1259,9 +1259,11 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
if (!attrsOnly)
{
if (!isConstraint)
- appendStringInfo(&buf, "CREATE %sINDEX %s ON %s USING %s (",+ appendStringInfo(&buf, "CREATE %sINDEX %s ON %s%s USING %s (",
idxrec->indisunique ? "UNIQUE " : "",
quote_identifier(NameStr(idxrelrec->relname)),
+ idxrelrec->relkind == RELKIND_PARTITIONED_INDEX ?+ "ONLY " : "",
generate_relation_name(indrelid, NIL),
quote_identifier(NameStr(amrec->amname)));
else /* currently, must be EXCLUDE constraint */
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.cindex 59280cd8bb..bddcbfc54f 100644--- a/src/backend/utils/cache/relcache.c+++ b/src/backend/utils/cache/relcache.c@@ -430,6 +430,7 @@ static void
RelationParseRelOptions(Relation relation, HeapTuple tuple)
{
bytea *options;
+ bool isindex;
relation->rd_options = NULL;
@@ -439,6 +440,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
case RELKIND_INDEX:
+ case RELKIND_PARTITIONED_INDEX:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_PARTITIONED_TABLE:
@@ -452,10 +454,12 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
* we might not have any other for pg_class yet (consider executing this
* code for pg_class itself)
*/
+ isindex = relation->rd_rel->relkind == RELKIND_INDEX ||+ relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX;
options = extractRelOptions(tuple,
GetPgClassDescriptor(),
- relation->rd_rel->relkind == RELKIND_INDEX ?- relation->rd_amroutine->amoptions : NULL);+ isindex ? relation->rd_amroutine->amoptions :+ NULL);
/*
* Copy parsed data into CacheMemoryContext. To guard against the
@@ -2045,7 +2049,8 @@ RelationIdGetRelation(Oid relationId)
* and we don't want to use the full-blown procedure because it's
* a headache for indexes that reload itself depends on.
*/
- if (rd->rd_rel->relkind == RELKIND_INDEX)+ if (rd->rd_rel->relkind == RELKIND_INDEX ||+ rd->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
RelationReloadIndexInfo(rd);
else
RelationClearRelation(rd, true);
@@ -2159,7 +2164,8 @@ RelationReloadIndexInfo(Relation relation)
Form_pg_class relp;
/* Should be called only for invalidated indexes */
- Assert(relation->rd_rel->relkind == RELKIND_INDEX &&+ Assert((relation->rd_rel->relkind == RELKIND_INDEX ||+ relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) &&
!relation->rd_isvalid);
/* Ensure it's closed at smgr level */
@@ -2379,7 +2385,8 @@ RelationClearRelation(Relation relation, bool rebuild)
{
RelationInitPhysicalAddr(relation);
- if (relation->rd_rel->relkind == RELKIND_INDEX)+ if (relation->rd_rel->relkind == RELKIND_INDEX ||+ relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
{
relation->rd_isvalid = false; /* needs to be revalidated */
if (relation->rd_refcnt > 1 && IsTransactionState())
@@ -2395,7 +2402,8 @@ RelationClearRelation(Relation relation, bool rebuild)
* re-read the pg_class row to handle possible physical relocation of the
* index, and we check for pg_index updates too.
*/
- if (relation->rd_rel->relkind == RELKIND_INDEX &&+ if ((relation->rd_rel->relkind == RELKIND_INDEX ||+ relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) &&
relation->rd_refcnt > 0 &&
relation->rd_indexcxt != NULL)
{
@@ -5453,7 +5461,10 @@ load_relcache_init_file(bool shared)
rel->rd_att->constr = constr;
}
- /* If it's an index, there's more to do */+ /*+ * If it's an index, there's more to do. Note we explicitly ignore+ * partitioned indexes here.+ */
if (rel->rd_rel->relkind == RELKIND_INDEX)
{
MemoryContext indexcxt;
@@ -5815,7 +5826,10 @@ write_relcache_init_file(bool shared)
(rel->rd_options ? VARSIZE(rel->rd_options) : 0),
fp);
- /* If it's an index, there's more to do */+ /*+ * If it's an index, there's more to do. Note we explicitly ignore+ * partitioned indexes here.+ */
if (rel->rd_rel->relkind == RELKIND_INDEX)
{
/* write the pg_index tuple */
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.cindex 4b47951de1..7c379b8110 100644--- a/src/bin/pg_dump/common.c+++ b/src/bin/pg_dump/common.c@@ -68,6 +68,7 @@ static int numextmembers;
static void flagInhTables(Archive *fout, TableInfo *tbinfo, int numTables,
InhInfo *inhinfo, int numInherits);
+static void flagInhIndexes(Archive *fout, TableInfo *tblinfo, int numTables);
static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
static DumpableObject **buildIndexArray(void *objArray, int numObjs,
Size objSize);
@@ -76,6 +77,8 @@ static int ExtensionMemberIdCompare(const void *p1, const void *p2);
static void findParentsByOid(TableInfo *self,
InhInfo *inhinfo, int numInherits);
static int strInArray(const char *pattern, char **arr, int arr_size);
+static IndxInfo *findIndexByOid(Oid oid, DumpableObject **idxinfoindex,+ int numIndexes);
/*
@@ -258,6 +261,10 @@ getSchemaData(Archive *fout, int *numTablesPtr)
getIndexes(fout, tblinfo, numTables);
if (g_verbose)
+ write_msg(NULL, "flagging indexes in partitioned tables\n");+ flagInhIndexes(fout, tblinfo, numTables);++ if (g_verbose)
write_msg(NULL, "reading extended statistics\n");
getExtendedStatistics(fout, tblinfo, numTables);
@@ -354,6 +361,57 @@ flagInhTables(Archive *fout, TableInfo *tblinfo, int numTables,
}
}
+/*+ * flagInhIndexes -+ * Fill in each partitioned index's ->parentidx pointer.+ */+static void+flagInhIndexes(Archive *fout, TableInfo *tblinfo, int numTables)+{+ int i,+ j;+ DumpableObject ***parentIndexArray;++ parentIndexArray = (DumpableObject ***)+ pg_malloc0(getMaxDumpId() * sizeof(DumpableObject **));++ for (i = 0; i < numTables; i++)+ {+ TableInfo *parenttbl;++ if (!tblinfo[i].ispartition || tblinfo[i].numParents == 0)+ continue;++ Assert(tblinfo[i].numParents == 1);+ parenttbl = tblinfo[i].parents[0];++ if (parentIndexArray[parenttbl->dobj.dumpId] == NULL)+ parentIndexArray[parenttbl->dobj.dumpId] =+ buildIndexArray(parenttbl->indexes,+ parenttbl->numIndexes,+ sizeof(IndxInfo));++ for (j = 0; j < tblinfo[i].numIndexes; j++)+ {+ IndxInfo *index = &(tblinfo[i].indexes[j]);++ if (index->indparentidx == 0)+ continue;++ index->parentidx =+ findIndexByOid(index->indparentidx,+ parentIndexArray[parenttbl->dobj.dumpId],+ parenttbl->numIndexes);+ }+ }++ for (i = 0; i < numTables; i++)+ if (parentIndexArray[i])+ pg_free(parentIndexArray[i]);++ pg_free(parentIndexArray);+}+
/* flagInhAttrs -
* for each dumpable table in tblinfo, flag its inherited attributes
*
@@ -827,6 +885,18 @@ findExtensionByOid(Oid oid)
return (ExtensionInfo *) findObjectByOid(oid, extinfoindex, numExtensions);
}
+/*+ * findIndexByOid+ * find the entry of the index with the given oid+ *+ * This one's signature is different from the previous ones because we lack a+ * global array of all indexes, so caller must pass their array as argument.+ */+static IndxInfo *+findIndexByOid(Oid oid, DumpableObject **idxinfoindex, int numIndexes)+{+ return (IndxInfo *) findObjectByOid(oid, idxinfoindex, numIndexes);+}
/*
* setExtensionMembership
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.cindex d8fb356130..458fd5657d 100644--- a/src/bin/pg_dump/pg_dump.c+++ b/src/bin/pg_dump/pg_dump.c@@ -6509,6 +6509,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
int i_tableoid,
i_oid,
i_indexname,
+ i_parentidx,
i_indexdef,
i_indnkeys,
i_indkey,
@@ -6530,10 +6531,6 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
{
TableInfo *tbinfo = &tblinfo[i];
- /* Only plain tables and materialized views have indexes. */- if (tbinfo->relkind != RELKIND_RELATION &&- tbinfo->relkind != RELKIND_MATVIEW)- continue;
if (!tbinfo->hasindex)
continue;
@@ -6561,7 +6558,35 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
* is not.
*/
resetPQExpBuffer(query);
- if (fout->remoteVersion >= 90400)+ if (fout->remoteVersion >= 11000)+ {+ appendPQExpBuffer(query,+ "SELECT t.tableoid, t.oid, "+ "t.relname AS indexname, "+ "i.indparentidx, "+ "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "+ "t.relnatts AS indnkeys, "+ "i.indkey, i.indisclustered, "+ "i.indisreplident, t.relpages, "+ "c.contype, c.conname, "+ "c.condeferrable, c.condeferred, "+ "c.tableoid AS contableoid, "+ "c.oid AS conoid, "+ "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, "+ "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "+ "t.reloptions AS indreloptions "+ "FROM pg_catalog.pg_index i "+ "JOIN pg_catalog.pg_class t ON (t.oid = i.indexrelid) "+ "LEFT JOIN pg_catalog.pg_constraint c "+ "ON (i.indrelid = c.conrelid AND "+ "i.indexrelid = c.conindid AND "+ "c.contype IN ('p','u','x')) "+ "WHERE i.indrelid = '%u'::pg_catalog.oid "+ "AND i.indisvalid AND i.indisready "+ "ORDER BY indexname",+ tbinfo->dobj.catId.oid);+ }+ else if (fout->remoteVersion >= 90400)
{
/*
* the test on indisready is necessary in 9.2, and harmless in
@@ -6570,6 +6595,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
appendPQExpBuffer(query,
"SELECT t.tableoid, t.oid, "
"t.relname AS indexname, "
+ "0 AS indparentidx, "
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
"t.relnatts AS indnkeys, "
"i.indkey, i.indisclustered, "
@@ -6601,6 +6627,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
appendPQExpBuffer(query,
"SELECT t.tableoid, t.oid, "
"t.relname AS indexname, "
+ "0 AS indparentidx, "
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
"t.relnatts AS indnkeys, "
"i.indkey, i.indisclustered, "
@@ -6628,6 +6655,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
appendPQExpBuffer(query,
"SELECT t.tableoid, t.oid, "
"t.relname AS indexname, "
+ "0 AS indparentidx, "
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
"t.relnatts AS indnkeys, "
"i.indkey, i.indisclustered, "
@@ -6658,6 +6686,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
appendPQExpBuffer(query,
"SELECT t.tableoid, t.oid, "
"t.relname AS indexname, "
+ "0 AS indparentidx, "
"pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
"t.relnatts AS indnkeys, "
"i.indkey, i.indisclustered, "
@@ -6690,6 +6719,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_tableoid = PQfnumber(res, "tableoid");
i_oid = PQfnumber(res, "oid");
i_indexname = PQfnumber(res, "indexname");
+ i_parentidx = PQfnumber(res, "indparentidx");
i_indexdef = PQfnumber(res, "indexdef");
i_indnkeys = PQfnumber(res, "indnkeys");
i_indkey = PQfnumber(res, "indkey");
@@ -6706,8 +6736,10 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_tablespace = PQfnumber(res, "tablespace");
i_indreloptions = PQfnumber(res, "indreloptions");
- indxinfo = (IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo));+ tbinfo->indexes = indxinfo =+ (IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo));
constrinfo = (ConstraintInfo *) pg_malloc(ntups * sizeof(ConstraintInfo));
+ tbinfo->numIndexes = ntups;
for (j = 0; j < ntups; j++)
{
@@ -6729,6 +6761,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
indxinfo[j].indkeys, indxinfo[j].indnkeys);
indxinfo[j].indisclustered = (PQgetvalue(res, j, i_indisclustered)[0] == 't');
indxinfo[j].indisreplident = (PQgetvalue(res, j, i_indisreplident)[0] == 't');
+ indxinfo[j].indparentidx = atooid(PQgetvalue(res, j, i_parentidx));+ indxinfo[j].parentidx = NULL; /* set in flagInhIndexes */
indxinfo[j].relpages = atoi(PQgetvalue(res, j, i_relpages));
contype = *(PQgetvalue(res, j, i_contype));
@@ -16123,6 +16157,17 @@ dumpIndex(Archive *fout, IndxInfo *indxinfo)
fmtId(indxinfo->dobj.name));
}
+ /* If the index is a partition, attach it to its parent */+ if (indxinfo->parentidx)+ {+ appendPQExpBuffer(q, "\nALTER INDEX %s ",+ fmtQualifiedId(fout->remoteVersion,+ indxinfo->parentidx->dobj.namespace->dobj.name,+ indxinfo->parentidx->dobj.name));+ appendPQExpBuffer(q, "ATTACH PARTITION %s;\n",+ fmtId(indxinfo->dobj.name));+ }+
/*
* DROP must be fully qualified in case same name appears in
* pg_catalog
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.hindex da884ffd09..cdeda0a840 100644--- a/src/bin/pg_dump/pg_dump.h+++ b/src/bin/pg_dump/pg_dump.h@@ -328,6 +328,8 @@ typedef struct _tableInfo
*/
int numParents; /* number of (immediate) parent tables */
struct _tableInfo **parents; /* TableInfos of immediate parents */
+ int numIndexes; /* number of indexes */+ struct _indxInfo *indexes; /* indexes */
struct _tableDataInfo *dataObj; /* TableDataInfo, if dumping its data */
int numTriggers; /* number of triggers for table */
struct _triggerInfo *triggers; /* array of TriggerInfo structs */
@@ -361,6 +363,8 @@ typedef struct _indxInfo
Oid *indkeys;
bool indisclustered;
bool indisreplident;
+ Oid indparentidx; /* if partitioned, parent index OID */+ struct _indxInfo *parentidx; /* link to parent index (initially NULL) */
/* if there is an associated constraint object, its dumpId: */
DumpId indexconstraint;
int relpages; /* relpages of the underlying table */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.cindex b7b978a361..45502fe07c 100644--- a/src/bin/psql/describe.c+++ b/src/bin/psql/describe.c@@ -1703,7 +1703,8 @@ describeOneTableDetails(const char *schemaname,
appendPQExpBufferStr(&buf, ",\n a.attidentity");
else
appendPQExpBufferStr(&buf, ",\n ''::pg_catalog.char AS attidentity");
- if (tableinfo.relkind == RELKIND_INDEX)+ if (tableinfo.relkind == RELKIND_INDEX ||+ tableinfo.relkind == RELKIND_PARTITIONED_INDEX)
appendPQExpBufferStr(&buf, ",\n pg_catalog.pg_get_indexdef(a.attrelid, a.attnum, TRUE) AS indexdef");
else
appendPQExpBufferStr(&buf, ",\n NULL AS indexdef");
@@ -1764,6 +1765,7 @@ describeOneTableDetails(const char *schemaname,
schemaname, relationname);
break;
case RELKIND_INDEX:
+ case RELKIND_PARTITIONED_INDEX:
if (tableinfo.relpersistence == 'u')
printfPQExpBuffer(&title, _("Unlogged index \"%s.%s\""),
schemaname, relationname);
@@ -1821,7 +1823,8 @@ describeOneTableDetails(const char *schemaname,
show_column_details = true;
}
- if (tableinfo.relkind == RELKIND_INDEX)+ if (tableinfo.relkind == RELKIND_INDEX ||+ tableinfo.relkind == RELKIND_PARTITIONED_INDEX)
headers[cols++] = gettext_noop("Definition");
if (tableinfo.relkind == RELKIND_FOREIGN_TABLE && pset.sversion >= 90200)
@@ -1832,6 +1835,7 @@ describeOneTableDetails(const char *schemaname,
headers[cols++] = gettext_noop("Storage");
if (tableinfo.relkind == RELKIND_RELATION ||
tableinfo.relkind == RELKIND_INDEX ||
+ tableinfo.relkind == RELKIND_PARTITIONED_INDEX ||
tableinfo.relkind == RELKIND_MATVIEW ||
tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
@@ -1904,7 +1908,8 @@ describeOneTableDetails(const char *schemaname,
}
/* Expression for index column */
- if (tableinfo.relkind == RELKIND_INDEX)+ if (tableinfo.relkind == RELKIND_INDEX ||+ tableinfo.relkind == RELKIND_PARTITIONED_INDEX)
printTableAddCell(&cont, PQgetvalue(res, i, 7), false, false);
/* FDW options for foreign table column, only for 9.2 or later */
@@ -1928,6 +1933,7 @@ describeOneTableDetails(const char *schemaname,
/* Statistics target, if the relkind supports this feature */
if (tableinfo.relkind == RELKIND_RELATION ||
tableinfo.relkind == RELKIND_INDEX ||
+ tableinfo.relkind == RELKIND_PARTITIONED_INDEX ||
tableinfo.relkind == RELKIND_MATVIEW ||
tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
@@ -2019,7 +2025,8 @@ describeOneTableDetails(const char *schemaname,
PQclear(result);
}
- if (tableinfo.relkind == RELKIND_INDEX)+ if (tableinfo.relkind == RELKIND_INDEX ||+ tableinfo.relkind == RELKIND_PARTITIONED_INDEX)
{
/* Footer information about an index */
PGresult *result;
@@ -3372,6 +3379,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" WHEN 's' THEN '%s'"
" WHEN " CppAsString2(RELKIND_FOREIGN_TABLE) " THEN '%s'"
" WHEN " CppAsString2(RELKIND_PARTITIONED_TABLE) " THEN '%s'"
+ " WHEN " CppAsString2(RELKIND_PARTITIONED_INDEX) " THEN '%s'"
" END as \"%s\",\n"
" pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
gettext_noop("Schema"),
@@ -3384,6 +3392,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("special"),
gettext_noop("foreign table"),
gettext_noop("table"), /* partitioned table */
+ gettext_noop("index"), /* partitioned index */
gettext_noop("Type"),
gettext_noop("Owner"));
@@ -3429,7 +3438,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
if (showMatViews)
appendPQExpBufferStr(&buf, CppAsString2(RELKIND_MATVIEW) ",");
if (showIndexes)
- appendPQExpBufferStr(&buf, CppAsString2(RELKIND_INDEX) ",");+ appendPQExpBufferStr(&buf, CppAsString2(RELKIND_INDEX) ","+ CppAsString2(RELKIND_PARTITIONED_INDEX) ",");
if (showSeq)
appendPQExpBufferStr(&buf, CppAsString2(RELKIND_SEQUENCE) ",");
if (showSystem || pattern)
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.hindex bd4014a69d..b608dac156 100644--- a/src/include/catalog/catversion.h+++ b/src/include/catalog/catversion.h@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 201711092+#define CATALOG_VERSION_NO 201711101
#endif
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.hindex ab1f9ef1bd..8438ffe4ce 100644--- a/src/include/catalog/index.h+++ b/src/include/catalog/index.h@@ -47,10 +47,12 @@ extern void index_check_primary_key(Relation heapRel,
#define INDEX_CREATE_SKIP_BUILD (1 << 2)
#define INDEX_CREATE_CONCURRENT (1 << 3)
#define INDEX_CREATE_IF_NOT_EXISTS (1 << 4)
+#define INDEX_CREATE_PARTITIONED (1 << 5)
extern Oid index_create(Relation heapRelation,
const char *indexRelationName,
Oid indexRelationId,
+ Oid parentIndexRelid,
Oid relFileNode,
IndexInfo *indexInfo,
List *indexColNames,
@@ -84,6 +86,8 @@ extern void index_drop(Oid indexId, bool concurrent);
extern IndexInfo *BuildIndexInfo(Relation index);
+extern bool CompareIndexInfo(IndexInfo *info1, IndexInfo *info2, AttrNumber *attmap);+
extern void BuildSpeculativeIndexInfo(Relation index, IndexInfo *ii);
extern void FormIndexDatum(IndexInfo *indexInfo,
@@ -134,4 +138,6 @@ extern bool ReindexIsProcessingHeap(Oid heapOid);
extern bool ReindexIsProcessingIndex(Oid indexOid);
extern Oid IndexGetRelation(Oid indexId, bool missing_ok);
+extern void indexSetParentIndex(Relation idx, Oid parentOid);+
#endif /* INDEX_H */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.hindex b256657bda..dd8e7ea2b5 100644--- a/src/include/catalog/pg_class.h+++ b/src/include/catalog/pg_class.h@@ -166,6 +166,7 @@ DESCR("");
#define RELKIND_COMPOSITE_TYPE 'c' /* composite type */
#define RELKIND_FOREIGN_TABLE 'f' /* foreign table */
#define RELKIND_PARTITIONED_TABLE 'p' /* partitioned table */
+#define RELKIND_PARTITIONED_INDEX 'I' /* partitioned index */
#define RELPERSISTENCE_PERMANENT 'p' /* regular table */
#define RELPERSISTENCE_UNLOGGED 'u' /* unlogged permanent table */
diff --git a/src/include/catalog/pg_index.h b/src/include/catalog/pg_index.hindex 8505c3be5f..e7afb0b921 100644--- a/src/include/catalog/pg_index.h+++ b/src/include/catalog/pg_index.h@@ -32,6 +32,7 @@ CATALOG(pg_index,2610) BKI_WITHOUT_OIDS BKI_SCHEMA_MACRO
{
Oid indexrelid; /* OID of the index */
Oid indrelid; /* OID of the relation it indexes */
+ Oid indparentidx; /* OID of parent index, 0 if not partitioned */
int16 indnatts; /* number of columns in index */
bool indisunique; /* is this a unique index? */
bool indisprimary; /* is this index for primary key? */
@@ -70,26 +71,27 @@ typedef FormData_pg_index *Form_pg_index;
* compiler constants for pg_index
* ----------------
*/
-#define Natts_pg_index 19+#define Natts_pg_index 20
#define Anum_pg_index_indexrelid 1
#define Anum_pg_index_indrelid 2
-#define Anum_pg_index_indnatts 3-#define Anum_pg_index_indisunique 4-#define Anum_pg_index_indisprimary 5-#define Anum_pg_index_indisexclusion 6-#define Anum_pg_index_indimmediate 7-#define Anum_pg_index_indisclustered 8-#define Anum_pg_index_indisvalid 9-#define Anum_pg_index_indcheckxmin 10-#define Anum_pg_index_indisready 11-#define Anum_pg_index_indislive 12-#define Anum_pg_index_indisreplident 13-#define Anum_pg_index_indkey 14-#define Anum_pg_index_indcollation 15-#define Anum_pg_index_indclass 16-#define Anum_pg_index_indoption 17-#define Anum_pg_index_indexprs 18-#define Anum_pg_index_indpred 19+#define Anum_pg_index_indparentidx 3+#define Anum_pg_index_indnatts 4+#define Anum_pg_index_indisunique 5+#define Anum_pg_index_indisprimary 6+#define Anum_pg_index_indisexclusion 7+#define Anum_pg_index_indimmediate 8+#define Anum_pg_index_indisclustered 9+#define Anum_pg_index_indisvalid 10+#define Anum_pg_index_indcheckxmin 11+#define Anum_pg_index_indisready 12+#define Anum_pg_index_indislive 13+#define Anum_pg_index_indisreplident 14+#define Anum_pg_index_indkey 15+#define Anum_pg_index_indcollation 16+#define Anum_pg_index_indclass 17+#define Anum_pg_index_indoption 18+#define Anum_pg_index_indexprs 19+#define Anum_pg_index_indpred 20
/*
* Index AMs that support ordered scans must support these two indoption
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.hindex bfead9af3d..fd2978f470 100644--- a/src/include/commands/defrem.h+++ b/src/include/commands/defrem.h@@ -25,12 +25,13 @@ extern void RemoveObjects(DropStmt *stmt);
extern ObjectAddress DefineIndex(Oid relationId,
IndexStmt *stmt,
Oid indexRelationId,
+ Oid parentIndexId,
bool is_alter_table,
bool check_rights,
bool check_not_in_use,
bool skip_build,
bool quiet);
-extern Oid ReindexIndex(RangeVar *indexRelation, int options);+extern void ReindexIndex(RangeVar *indexRelation, int options);
extern Oid ReindexTable(RangeVar *relation, int options);
extern void ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
int options);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.hindex b4ce3548dc..6751d7e0c0 100644--- a/src/include/nodes/parsenodes.h+++ b/src/include/nodes/parsenodes.h@@ -839,7 +839,7 @@ typedef struct PartitionRangeDatum
} PartitionRangeDatum;
/*
- * PartitionCmd - info for ALTER TABLE ATTACH/DETACH PARTITION commands+ * PartitionCmd - info for ALTER TABLE/INDEX ATTACH/DETACH PARTITION commands
*/
typedef struct PartitionCmd
{
@@ -2700,7 +2700,8 @@ typedef struct FetchStmt
* properties are empty.
*
* The relation to build the index on can be represented either by name
- * or by OID.+ * (in which case the RangeVar indicates whether to recurse or not) or by OID+ * (in which case the command is always recursive).
* ----------------------
*/
typedef struct IndexStmt
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.outindex 11f0baa11b..5a96b0b5e1 100644--- a/src/test/regress/expected/alter_table.out+++ b/src/test/regress/expected/alter_table.out@@ -3276,7 +3276,7 @@ CREATE TABLE unparted (
);
CREATE TABLE fail_part (like unparted);
ALTER TABLE unparted ATTACH PARTITION fail_part FOR VALUES IN ('a');
-ERROR: "unparted" is not partitioned+ERROR: table "unparted" is not partitioned
DROP TABLE unparted, fail_part;
-- check that partition bound is compatible
CREATE TABLE list_parted (
@@ -3656,7 +3656,7 @@ DROP TABLE fail_part;
-- check that the table is partitioned at all
CREATE TABLE regular_table (a int);
ALTER TABLE regular_table DETACH PARTITION any_name;
-ERROR: "regular_table" is not partitioned+ERROR: table "regular_table" is not partitioned
DROP TABLE regular_table;
-- check that the partition being detached exists at all
ALTER TABLE list_parted2 DETACH PARTITION part_4;
diff --git a/src/test/regress/expected/indexing.out b/src/test/regress/expected/indexing.outnew file mode 100644index 0000000000..cde29bebb5--- /dev/null+++ b/src/test/regress/expected/indexing.out@@ -0,0 +1,330 @@+-- Creating an index on a partitioned table makes the partitions+-- automatically get the index+create table idxpart (a int, b int, c text) partition by range (a);+create table idxpart1 partition of idxpart for values from (0) to (10);+create table idxpart2 partition of idxpart for values from (10) to (100)+ partition by range (b);+create table idxpart21 partition of idxpart2 for values from (0) to (100);+create index on idxpart (a);+select relname, relkind, indparentidx::regclass+ from pg_class left join pg_index on (indexrelid = oid)+ where relname like 'idxpart%' order by relname;+ relname | relkind | indparentidx +-----------------+---------+----------------+ idxpart | p | + idxpart1 | r | + idxpart1_a_idx | i | idxpart_a_idx+ idxpart2 | p | + idxpart21 | r | + idxpart21_a_idx | i | idxpart2_a_idx+ idxpart2_a_idx | I | idxpart_a_idx+ idxpart_a_idx | I | -+(8 rows)++drop table idxpart;+-- Some unsupported cases+create table idxpart (a int, b int, c text) partition by range (a);+create table idxpart1 partition of idxpart for values from (0) to (10);+create unique index on idxpart (a);+ERROR: cannot create unique index on partitioned table "idxpart"+create index concurrently on idxpart (a);+ERROR: cannot create index on partitioned table "idxpart" concurrently+drop table idxpart;+-- If a table without index is attached as partition to a table with+-- an index, the index is automatically created+create table idxpart (a int, b int, c text) partition by range (a);+create index idxparti on idxpart (a);+create index idxparti2 on idxpart (b, c);+create table idxpart1 (like idxpart);+\d idxpart1+ Table "public.idxpart1"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | + b | integer | | | + c | text | | | ++alter table idxpart attach partition idxpart1 for values from (0) to (10);+\d idxpart1+ Table "public.idxpart1"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | + b | integer | | | + c | text | | | +Partition of: idxpart FOR VALUES FROM (0) TO (10)+Indexes:+ "idxpart1_a_idx" btree (a)+ "idxpart1_b_c_idx" btree (b, c)++drop table idxpart;+-- If a partition already has an index, make sure we don't create a separate+-- one+create table idxpart (a int, b int) partition by range (a, b);+create table idxpart1 partition of idxpart for values from (0, 0) to (10, 10);+create index on idxpart1 (a, b);+create index on idxpart (a, b);+\d idxpart1+ Table "public.idxpart1"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | + b | integer | | | +Partition of: idxpart FOR VALUES FROM (0, 0) TO (10, 10)+Indexes:+ "idxpart1_a_b_idx" btree (a, b)++select indexrelid::regclass, indrelid::regclass, indparentidx::regclass+ from pg_index where indexrelid::regclass::text like 'idxpart%'+ order by indexrelid::regclass;+ indexrelid | indrelid | indparentidx +------------------+----------+-----------------+ idxpart1_a_b_idx | idxpart1 | idxpart_a_b_idx+ idxpart_a_b_idx | idxpart | -+(2 rows)++create index idxpart1_tst1 on idxpart1 (b, a);+create index idxpart1_tst2 on idxpart1 using hash (a);+create index idxpart1_tst3 on idxpart1 (a, b) where a > 10;+-- ALTER INDEX .. ATTACH/DETACH, error cases+alter index idxpart attach partition idxpart1;+ERROR: "idxpart" is not an index+alter index idxpart detach partition idxpart1;+ERROR: "idxpart" is not an index+alter index idxpart_a_b_idx attach partition idxpart1;+ERROR: "idxpart1" is not an index+alter index idxpart_a_b_idx detach partition idxpart1;+ERROR: "idxpart1" is not an index+alter index idxpart_a_b_idx attach partition idxpart_a_b_idx;+ERROR: index "idxpart_a_b_idx" is not on a partition of table "idxpart"+alter index idxpart_a_b_idx attach partition idxpart1_b_idx;+ERROR: relation "idxpart1_b_idx" does not exist+alter index idxpart_a_b_idx attach partition idxpart1_tst1;+ERROR: definition of index "idxpart1_tst1" is not compatible with index "idxpart_a_b_idx"+alter index idxpart_a_b_idx attach partition idxpart1_tst2;+ERROR: definition of index "idxpart1_tst2" is not compatible with index "idxpart_a_b_idx"+alter index idxpart_a_b_idx attach partition idxpart1_tst3;+ERROR: definition of index "idxpart1_tst3" is not compatible with index "idxpart_a_b_idx"+-- OK+alter index idxpart_a_b_idx attach partition idxpart1_a_b_idx;+alter index idxpart_a_b_idx attach partition idxpart1_a_b_idx; -- quiet+alter index idxpart_a_b_idx detach partition idxpart1_a_b_idx;+alter index idxpart_a_b_idx detach partition idxpart1_a_b_idx; -- quiet+drop table idxpart;+-- make sure everything's gone+select indexrelid::regclass, indrelid::regclass, indparentidx::regclass+ from pg_index where indexrelid::regclass::text like 'idxpart%';+ indexrelid | indrelid | indparentidx +------------+----------+--------------+(0 rows)++-- If CREATE INDEX ONLY, don't create indexes on partitions; and existing+-- indexes on partitions don't change parent. ALTER INDEX ATTACH can change+-- the parent after the fact.+create table idxpart (a int) partition by range (a);+create table idxpart1 partition of idxpart for values from (0) to (100);+create table idxpart2 partition of idxpart for values from (100) to (1000)+ partition by range (a);+create table idxpart21 partition of idxpart2 for values from (100) to (200);+create table idxpart22 partition of idxpart2 for values from (200) to (300);+create index on idxpart22 (a);+create index on only idxpart2 (a);+create index on idxpart (a);+-- Here we expect that idxpart1 and idxpart2 have a new index, but idxpart21+-- does not; also, idxpart22 is detached.+\d idxpart1+ Table "public.idxpart1"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | +Partition of: idxpart FOR VALUES FROM (0) TO (100)+Indexes:+ "idxpart1_a_idx" btree (a)++\d idxpart2+ Table "public.idxpart2"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | +Partition of: idxpart FOR VALUES FROM (100) TO (1000)+Partition key: RANGE (a)+Indexes:+ "idxpart2_a_idx" btree (a)+Number of partitions: 2 (Use \d+ to list them.)++\d idxpart21+ Table "public.idxpart21"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | +Partition of: idxpart2 FOR VALUES FROM (100) TO (200)++select indexrelid::regclass, indrelid::regclass, indparentidx::regclass+ from pg_index where indexrelid::regclass::text like 'idxpart%'+ order by indrelid::regclass;+ indexrelid | indrelid | indparentidx +-----------------+-----------+---------------+ idxpart_a_idx | idxpart | -+ idxpart1_a_idx | idxpart1 | idxpart_a_idx+ idxpart2_a_idx | idxpart2 | idxpart_a_idx+ idxpart22_a_idx | idxpart22 | -+(4 rows)++alter index idxpart2_a_idx attach partition idxpart22_a_idx;+select indexrelid::regclass, indrelid::regclass, indparentidx::regclass+ from pg_index where indexrelid::regclass::text like 'idxpart%'+ order by indrelid::regclass;+ indexrelid | indrelid | indparentidx +-----------------+-----------+----------------+ idxpart_a_idx | idxpart | -+ idxpart1_a_idx | idxpart1 | idxpart_a_idx+ idxpart2_a_idx | idxpart2 | idxpart_a_idx+ idxpart22_a_idx | idxpart22 | idxpart2_a_idx+(4 rows)++drop table idxpart;+-- When a table is attached a partition and it already has an index, a+-- duplicate index should not get created, but rather the index becomes+-- attached to the parent's index.+create table idxpart (a int, b int, c text) partition by range (a);+create index idxparti on idxpart (a);+create index idxparti2 on idxpart (b, c);+create table idxpart1 (like idxpart including indexes);+\d idxpart1+ Table "public.idxpart1"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | + b | integer | | | + c | text | | | +Indexes:+ "idxpart1_a_idx" btree (a)+ "idxpart1_b_c_idx" btree (b, c)++select relname, relkind, indparentidx::regclass+ from pg_class left join pg_index on (indexrelid = oid)+ where relname like 'idxpart%' order by relname;+ relname | relkind | indparentidx +------------------+---------+--------------+ idxpart | p | + idxpart1 | r | + idxpart1_a_idx | i | -+ idxpart1_b_c_idx | i | -+ idxparti | I | -+ idxparti2 | I | -+(6 rows)++alter table idxpart attach partition idxpart1 for values from (0) to (10);+\d idxpart1+ Table "public.idxpart1"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | + b | integer | | | + c | text | | | +Partition of: idxpart FOR VALUES FROM (0) TO (10)+Indexes:+ "idxpart1_a_idx" btree (a)+ "idxpart1_b_c_idx" btree (b, c)++select relname, relkind, indparentidx::regclass+ from pg_class left join pg_index on (indexrelid = oid)+ where relname like 'idxpart%' order by relname;+ relname | relkind | indparentidx +------------------+---------+--------------+ idxpart | p | + idxpart1 | r | + idxpart1_a_idx | i | idxparti+ idxpart1_b_c_idx | i | idxparti2+ idxparti | I | -+ idxparti2 | I | -+(6 rows)++drop table idxpart;+-- Make sure the partition columns are mapped correctly+create table idxpart (a int, b int, c text) partition by range (a);+create index idxparti on idxpart (a);+create index idxparti2 on idxpart (c, b);+create table idxpart1 (c text, a int, b int);+alter table idxpart attach partition idxpart1 for values from (0) to (10);+\d idxpart1+ Table "public.idxpart1"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ c | text | | | + a | integer | | | + b | integer | | | +Partition of: idxpart FOR VALUES FROM (0) TO (10)+Indexes:+ "idxpart1_a_idx" btree (a)+ "idxpart1_c_b_idx" btree (c, b)++drop table idxpart;+-- Make sure things work if either table has dropped columns+create table idxpart (a int, b int, c int, d int) partition by range (a);+alter table idxpart drop column c;+create index idxparti on idxpart (a);+create index idxparti2 on idxpart (b, d);+create table idxpart1 (like idxpart);+alter table idxpart attach partition idxpart1 for values from (0) to (10);+\d idxpart1+ Table "public.idxpart1"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | + b | integer | | | + d | integer | | | +Partition of: idxpart FOR VALUES FROM (0) TO (10)+Indexes:+ "idxpart1_a_idx" btree (a)+ "idxpart1_b_d_idx" btree (b, d)++select attrelid::regclass, attname, attnum from pg_attribute+ where attrelid in ('idxpart'::regclass, 'idxpart1'::regclass) and attnum > 0+ order by attrelid::regclass, attnum;+ attrelid | attname | attnum +----------+------------------------------+--------+ idxpart | a | 1+ idxpart | b | 2+ idxpart | ........pg.dropped.3........ | 3+ idxpart | d | 4+ idxpart1 | a | 1+ idxpart1 | b | 2+ idxpart1 | d | 3+(7 rows)++drop table idxpart;+create table idxpart (a int, b int, c int) partition by range (a);+create table idxpart1 (zz int, like idxpart, aa int);+alter table idxpart1 drop column zz, drop column aa;+create index idxparti on idxpart (a);+create index idxparti2 on idxpart (b, c);+alter table idxpart attach partition idxpart1 for values from (0) to (10);+\d idxpart1+ Table "public.idxpart1"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | + b | integer | | | + c | integer | | | +Partition of: idxpart FOR VALUES FROM (0) TO (10)+Indexes:+ "idxpart1_a_idx" btree (a)+ "idxpart1_b_c_idx" btree (b, c)++select attrelid::regclass, attname, attnum from pg_attribute+ where attrelid in ('idxpart'::regclass, 'idxpart1'::regclass) and attnum > 0+ order by attrelid::regclass, attnum;+ attrelid | attname | attnum +----------+------------------------------+--------+ idxpart | a | 1+ idxpart | b | 2+ idxpart | c | 3+ idxpart1 | ........pg.dropped.1........ | 1+ idxpart1 | a | 2+ idxpart1 | b | 3+ idxpart1 | c | 4+ idxpart1 | ........pg.dropped.5........ | 5+(8 rows)++drop table idxpart;diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_scheduleindex aa5e6af621..591e7da337 100644--- a/src/test/regress/parallel_schedule+++ b/src/test/regress/parallel_schedule@@ -116,7 +116,7 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid c
# ----------
# Another group of parallel tests
# ----------
-test: identity partition_join reloptions+test: identity partition_join reloptions indexing
# event triggers cannot run concurrently with any test that runs DDL
test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_scheduleindex 3866314a92..d5b5ec8472 100644--- a/src/test/regress/serial_schedule+++ b/src/test/regress/serial_schedule@@ -181,5 +181,6 @@ test: xml
test: identity
test: partition_join
test: reloptions
+test: indexing
test: event_trigger
test: stats
diff --git a/src/test/regress/sql/indexing.sql b/src/test/regress/sql/indexing.sqlnew file mode 100644index 0000000000..53a28f120f--- /dev/null+++ b/src/test/regress/sql/indexing.sql@@ -0,0 +1,142 @@+-- Creating an index on a partitioned table makes the partitions+-- automatically get the index+create table idxpart (a int, b int, c text) partition by range (a);+create table idxpart1 partition of idxpart for values from (0) to (10);+create table idxpart2 partition of idxpart for values from (10) to (100)+ partition by range (b);+create table idxpart21 partition of idxpart2 for values from (0) to (100);+create index on idxpart (a);+select relname, relkind, indparentidx::regclass+ from pg_class left join pg_index on (indexrelid = oid)+ where relname like 'idxpart%' order by relname;+drop table idxpart;++-- Some unsupported cases+create table idxpart (a int, b int, c text) partition by range (a);+create table idxpart1 partition of idxpart for values from (0) to (10);+create unique index on idxpart (a);+create index concurrently on idxpart (a);+drop table idxpart;++-- If a table without index is attached as partition to a table with+-- an index, the index is automatically created+create table idxpart (a int, b int, c text) partition by range (a);+create index idxparti on idxpart (a);+create index idxparti2 on idxpart (b, c);+create table idxpart1 (like idxpart);+\d idxpart1+alter table idxpart attach partition idxpart1 for values from (0) to (10);+\d idxpart1+drop table idxpart;++-- If a partition already has an index, make sure we don't create a separate+-- one+create table idxpart (a int, b int) partition by range (a, b);+create table idxpart1 partition of idxpart for values from (0, 0) to (10, 10);+create index on idxpart1 (a, b);+create index on idxpart (a, b);+\d idxpart1+select indexrelid::regclass, indrelid::regclass, indparentidx::regclass+ from pg_index where indexrelid::regclass::text like 'idxpart%'+ order by indexrelid::regclass;+create index idxpart1_tst1 on idxpart1 (b, a);+create index idxpart1_tst2 on idxpart1 using hash (a);+create index idxpart1_tst3 on idxpart1 (a, b) where a > 10;++-- ALTER INDEX .. ATTACH/DETACH, error cases+alter index idxpart attach partition idxpart1;+alter index idxpart detach partition idxpart1;+alter index idxpart_a_b_idx attach partition idxpart1;+alter index idxpart_a_b_idx detach partition idxpart1;+alter index idxpart_a_b_idx attach partition idxpart_a_b_idx;+alter index idxpart_a_b_idx attach partition idxpart1_b_idx;+alter index idxpart_a_b_idx attach partition idxpart1_tst1;+alter index idxpart_a_b_idx attach partition idxpart1_tst2;+alter index idxpart_a_b_idx attach partition idxpart1_tst3;+-- OK+alter index idxpart_a_b_idx attach partition idxpart1_a_b_idx;+alter index idxpart_a_b_idx attach partition idxpart1_a_b_idx; -- quiet+alter index idxpart_a_b_idx detach partition idxpart1_a_b_idx;+alter index idxpart_a_b_idx detach partition idxpart1_a_b_idx; -- quiet+drop table idxpart;+-- make sure everything's gone+select indexrelid::regclass, indrelid::regclass, indparentidx::regclass+ from pg_index where indexrelid::regclass::text like 'idxpart%';++-- If CREATE INDEX ONLY, don't create indexes on partitions; and existing+-- indexes on partitions don't change parent. ALTER INDEX ATTACH can change+-- the parent after the fact.+create table idxpart (a int) partition by range (a);+create table idxpart1 partition of idxpart for values from (0) to (100);+create table idxpart2 partition of idxpart for values from (100) to (1000)+ partition by range (a);+create table idxpart21 partition of idxpart2 for values from (100) to (200);+create table idxpart22 partition of idxpart2 for values from (200) to (300);+create index on idxpart22 (a);+create index on only idxpart2 (a);+create index on idxpart (a);+-- Here we expect that idxpart1 and idxpart2 have a new index, but idxpart21+-- does not; also, idxpart22 is detached.+\d idxpart1+\d idxpart2+\d idxpart21+select indexrelid::regclass, indrelid::regclass, indparentidx::regclass+ from pg_index where indexrelid::regclass::text like 'idxpart%'+ order by indrelid::regclass;+alter index idxpart2_a_idx attach partition idxpart22_a_idx;+select indexrelid::regclass, indrelid::regclass, indparentidx::regclass+ from pg_index where indexrelid::regclass::text like 'idxpart%'+ order by indrelid::regclass;+drop table idxpart;++-- When a table is attached a partition and it already has an index, a+-- duplicate index should not get created, but rather the index becomes+-- attached to the parent's index.+create table idxpart (a int, b int, c text) partition by range (a);+create index idxparti on idxpart (a);+create index idxparti2 on idxpart (b, c);+create table idxpart1 (like idxpart including indexes);+\d idxpart1+select relname, relkind, indparentidx::regclass+ from pg_class left join pg_index on (indexrelid = oid)+ where relname like 'idxpart%' order by relname;+alter table idxpart attach partition idxpart1 for values from (0) to (10);+\d idxpart1+select relname, relkind, indparentidx::regclass+ from pg_class left join pg_index on (indexrelid = oid)+ where relname like 'idxpart%' order by relname;+drop table idxpart;++-- Make sure the partition columns are mapped correctly+create table idxpart (a int, b int, c text) partition by range (a);+create index idxparti on idxpart (a);+create index idxparti2 on idxpart (c, b);+create table idxpart1 (c text, a int, b int);+alter table idxpart attach partition idxpart1 for values from (0) to (10);+\d idxpart1+drop table idxpart;++-- Make sure things work if either table has dropped columns+create table idxpart (a int, b int, c int, d int) partition by range (a);+alter table idxpart drop column c;+create index idxparti on idxpart (a);+create index idxparti2 on idxpart (b, d);+create table idxpart1 (like idxpart);+alter table idxpart attach partition idxpart1 for values from (0) to (10);+\d idxpart1+select attrelid::regclass, attname, attnum from pg_attribute+ where attrelid in ('idxpart'::regclass, 'idxpart1'::regclass) and attnum > 0+ order by attrelid::regclass, attnum;+drop table idxpart;++create table idxpart (a int, b int, c int) partition by range (a);+create table idxpart1 (zz int, like idxpart, aa int);+alter table idxpart1 drop column zz, drop column aa;+create index idxparti on idxpart (a);+create index idxparti2 on idxpart (b, c);+alter table idxpart attach partition idxpart1 for values from (0) to (10);+\d idxpart1+select attrelid::regclass, attname, attnum from pg_attribute+ where attrelid in ('idxpart'::regclass, 'idxpart1'::regclass) and attnum > 0+ order by attrelid::regclass, attnum;+drop table idxpart;--
2.11.0
[text/plain] v4-0005-allow-indexes-on-partitioned-tables-to-be-unique.patch (20.7K, ../20171113175535.bvlfra6lm5rnoja2@alvherre.pgsql/6-v4-0005-allow-indexes-on-partitioned-tables-to-be-unique.patch)
download | inline diff:
From 01a710194dc129b711598764e5dddde1f9d14b54 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Mon, 6 Nov 2017 17:04:55 +0100
Subject: [PATCH v4 5/5] allow indexes on partitioned tables to be unique
---
src/backend/commands/indexcmds.c | 74 ++++++++++++++--
src/backend/parser/parse_utilcmd.c | 24 -----
src/test/regress/expected/alter_table.out | 8 --
src/test/regress/expected/create_table.out | 12 ---
src/test/regress/expected/indexing.out | 135 ++++++++++++++++++++++++++++-
src/test/regress/sql/alter_table.sql | 2 -
src/test/regress/sql/create_table.sql | 8 --
src/test/regress/sql/indexing.sql | 72 ++++++++++++++-
8 files changed, 269 insertions(+), 66 deletions(-)
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.cindex c289f62b9d..446c58368e 100644--- a/src/backend/commands/indexcmds.c+++ b/src/backend/commands/indexcmds.c@@ -425,20 +425,11 @@ DefineIndex(Oid relationId,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot create index on partitioned table \"%s\" concurrently",
RelationGetRelationName(rel))));
- if (stmt->unique)- ereport(ERROR,- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),- errmsg("cannot create unique index on partitioned table \"%s\"",- RelationGetRelationName(rel))));
if (stmt->excludeOpNames)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot create exclusion constraints on partitioned table \"%s\"",
RelationGetRelationName(rel))));
- if (is_alter_table)- ereport(ERROR,- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),- errmsg("cannot create constraints on partitioned tables")));
}
/*
@@ -635,6 +626,71 @@ DefineIndex(Oid relationId,
index_check_primary_key(rel, indexInfo, is_alter_table);
/*
+ * If this table is partitioned and we're creating a unique index or a+ * primary key, make sure that the indexed columns are part of the+ * partition key. Otherwise it would be possible to violate uniqueness by+ * putting values that ought to be unique in different partitions.+ *+ * We could lift this limitation if we had global indexes, but those have+ * their own problems, so this is a useful feature combination.+ *+ * XXX in some cases rel->rd_partkey is NULL, which caused this code to+ * crash. In what cases can that happen?+ */+ if (partitioned && (stmt->unique || stmt->primary) &&+ (rel->rd_partkey != NULL))+ {+ int i;+ PartitionKey key = rel->rd_partkey;++ /*+ * A partitioned table can have unique indexes, as long as all the+ * columns in the unique key appear in the partition key. Because+ * partitions are non-overlapping, this guarantees that there is a+ * single partition that can contain any given key, and thus the+ * uniqueness is guaranteed globally.+ */++ for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)+ {+ bool found;+ int j;++ /*+ * Expression indexes are not supported yet. We can probably use+ * pull_varattnos() on the expression, and verify that all the+ * vars mentioned correspond to columns of the partition key.+ */+ if (indexInfo->ii_KeyAttrNumbers[i] == 0)+ ereport(ERROR,+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),+ errmsg("UNIQUE indexes containing expressions are not supported")));++ found = false;+ for (j = 0; j < key->partnatts; j++)+ {+ if (indexInfo->ii_KeyAttrNumbers[i] == key->partattrs[j])+ {+ found = true;+ break; /* all good */+ }+ }+ if (!found)+ {+ char *attname;++ attname = NameStr(TupleDescAttr(RelationGetDescr(rel),+ indexInfo->ii_KeyAttrNumbers[i] - 1)->attname);+ ereport(ERROR,+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),+ errmsg("UNIQUE index on table \"%s\" contains column \"%s\" which is not part of the partition key",+ RelationGetRelationName(rel), attname)));+ }+ }+ }+++ /*
* We disallow indexes on system columns other than OID. They would not
* necessarily get updated correctly, and they don't seem useful anyway.
*/
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.cindex 938e73f8ad..364bd4c81d 100644--- a/src/backend/parser/parse_utilcmd.c+++ b/src/backend/parser/parse_utilcmd.c@@ -692,12 +692,6 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
- if (cxt->ispartitioned)- ereport(ERROR,- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),- errmsg("primary key constraints are not supported on partitioned tables"),- parser_errposition(cxt->pstate,- constraint->location)));
/* FALL THRU */
case CONSTR_UNIQUE:
@@ -707,12 +701,6 @@ transformColumnDefinition(CreateStmtContext *cxt, ColumnDef *column)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
- if (cxt->ispartitioned)- ereport(ERROR,- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),- errmsg("unique constraints are not supported on partitioned tables"),- parser_errposition(cxt->pstate,- constraint->location)));
if (constraint->keys == NIL)
constraint->keys = list_make1(makeString(column->colname));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
@@ -809,12 +797,6 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("primary key constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
- if (cxt->ispartitioned)- ereport(ERROR,- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),- errmsg("primary key constraints are not supported on partitioned tables"),- parser_errposition(cxt->pstate,- constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
@@ -825,12 +807,6 @@ transformTableConstraint(CreateStmtContext *cxt, Constraint *constraint)
errmsg("unique constraints are not supported on foreign tables"),
parser_errposition(cxt->pstate,
constraint->location)));
- if (cxt->ispartitioned)- ereport(ERROR,- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),- errmsg("unique constraints are not supported on partitioned tables"),- parser_errposition(cxt->pstate,- constraint->location)));
cxt->ixconstraints = lappend(cxt->ixconstraints, constraint);
break;
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.outindex 5a96b0b5e1..e7cf6ae470 100644--- a/src/test/regress/expected/alter_table.out+++ b/src/test/regress/expected/alter_table.out@@ -3229,14 +3229,6 @@ CREATE TABLE partitioned (
a int,
b int
) PARTITION BY RANGE (a, (a+b+1));
-ALTER TABLE partitioned ADD UNIQUE (a);-ERROR: unique constraints are not supported on partitioned tables-LINE 1: ALTER TABLE partitioned ADD UNIQUE (a);- ^-ALTER TABLE partitioned ADD PRIMARY KEY (a);-ERROR: primary key constraints are not supported on partitioned tables-LINE 1: ALTER TABLE partitioned ADD PRIMARY KEY (a);- ^
ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
ERROR: foreign key constraints are not supported on partitioned tables
LINE 1: ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.outindex 335cd37e18..a89e0c17df 100644--- a/src/test/regress/expected/create_table.out+++ b/src/test/regress/expected/create_table.out@@ -276,12 +276,6 @@ CREATE TABLE partitioned (
) PARTITION BY LIST (a1, a2); -- fail
ERROR: cannot use "list" partition strategy with more than one column
-- unsupported constraint type for partitioned tables
-CREATE TABLE partitioned (- a int PRIMARY KEY-) PARTITION BY RANGE (a);-ERROR: primary key constraints are not supported on partitioned tables-LINE 2: a int PRIMARY KEY- ^
CREATE TABLE pkrel (
a int PRIMARY KEY
);
@@ -293,12 +287,6 @@ LINE 2: a int REFERENCES pkrel(a)
^
DROP TABLE pkrel;
CREATE TABLE partitioned (
- a int UNIQUE-) PARTITION BY RANGE (a);-ERROR: unique constraints are not supported on partitioned tables-LINE 2: a int UNIQUE- ^-CREATE TABLE partitioned (
a int,
EXCLUDE USING gist (a WITH &&)
) PARTITION BY RANGE (a);
diff --git a/src/test/regress/expected/indexing.out b/src/test/regress/expected/indexing.outindex cde29bebb5..7ec8c5e3f7 100644--- a/src/test/regress/expected/indexing.out+++ b/src/test/regress/expected/indexing.out@@ -25,8 +25,6 @@ drop table idxpart;
-- Some unsupported cases
create table idxpart (a int, b int, c text) partition by range (a);
create table idxpart1 partition of idxpart for values from (0) to (10);
-create unique index on idxpart (a);-ERROR: cannot create unique index on partitioned table "idxpart"
create index concurrently on idxpart (a);
ERROR: cannot create index on partitioned table "idxpart" concurrently
drop table idxpart;
@@ -328,3 +326,136 @@ select attrelid::regclass, attname, attnum from pg_attribute
(8 rows)
drop table idxpart;
+--+-- Constraint-related indexes+--+-- Verify that it works to add primary key / unique to partitioned tables+create table idxpart (a int primary key, b int) partition by range (b);+ERROR: UNIQUE index on table "idxpart" contains column "a" which is not part of the partition key+create table idxpart (a int primary key, b int) partition by range (a);+\d idxpart+ Table "public.idxpart"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | not null | + b | integer | | | +Partition key: RANGE (a)+Indexes:+ "idxpart_pkey" PRIMARY KEY, btree (a)++drop table idxpart;+create table idxpart (a int unique, b int) partition by range (a);+\d idxpart+ Table "public.idxpart"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | + b | integer | | | +Partition key: RANGE (a)+Indexes:+ "idxpart_a_key" UNIQUE CONSTRAINT, btree (a)++drop table idxpart;+-- but not other types of index-based constraints+create table idxpart (a int, exclude (a with = )) partition by range (a);+ERROR: exclusion constraints are not supported on partitioned tables+LINE 1: create table idxpart (a int, exclude (a with = )) partition ...+ ^+-- It works to add primary keys after the partitioned table is created+create table idxpart (a int, b int) partition by range (a);+alter table idxpart add primary key (a);+\d idxpart+ Table "public.idxpart"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | not null | + b | integer | | | +Partition key: RANGE (a)+Indexes:+ "idxpart_pkey" PRIMARY KEY, btree (a)++drop table idxpart;+-- It works to add unique constraints after the partitioned table is created+create table idxpart (a int, b int) partition by range (a);+alter table idxpart add unique (a);+\d idxpart+ Table "public.idxpart"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | | + b | integer | | | +Partition key: RANGE (a)+Indexes:+ "idxpart_a_key" UNIQUE CONSTRAINT, btree (a)++drop table idxpart;+-- Exclusion constraints cannot be added+create table idxpart (a int, b int) partition by range (a);+alter table idxpart add exclude (a with =);+ERROR: exclusion constraints are not supported on partitioned tables+LINE 1: alter table idxpart add exclude (a with =);+ ^+drop table idxpart;+-- It is an error to add a constraint to columns that are not in the partition+-- key.+create table idxpart (a int, b int, primary key (a, b)) partition by range (a);+ERROR: UNIQUE index on table "idxpart" contains column "b" which is not part of the partition key+create table idxpart (a int, b int, primary key (a, b)) partition by range (b);+ERROR: UNIQUE index on table "idxpart" contains column "a" which is not part of the partition key+create table idxpart (a int, b int, unique (a, b)) partition by range (a);+ERROR: UNIQUE index on table "idxpart" contains column "b" which is not part of the partition key+create table idxpart (a int, b int, unique (a, b)) partition by range (b);+ERROR: UNIQUE index on table "idxpart" contains column "a" which is not part of the partition key+-- but using a partial set of columns is okay+create table idxpart (a int, b int, c int primary key) partition by range (b);+ERROR: UNIQUE index on table "idxpart" contains column "c" which is not part of the partition key+drop table idxpart;+ERROR: table "idxpart" does not exist+create table idxpart (a int, b int, primary key (a)) partition by range (a, b);+drop table idxpart;+create table idxpart (a int, b int, primary key (b)) partition by range (a, b);+drop table idxpart;+create table idxpart (a int, b int, c int unique) partition by range (b);+ERROR: UNIQUE index on table "idxpart" contains column "c" which is not part of the partition key+drop table idxpart;+ERROR: table "idxpart" does not exist+create table idxpart (a int, b int, unique (a)) partition by range (a, b);+drop table idxpart;+create table idxpart (a int, b int, unique (b)) partition by range (a, b);+drop table idxpart;+-- When (sub)partitions are created, they also contain the constraint+create table idxpart (a int, b int, primary key (a, b)) partition by range (a, b);+create table idxpart1 partition of idxpart for values from (1, 1) to (10, 10);+create table idxpart2 partition of idxpart for values from (10, 10) to (20, 20)+ partition by range (b);+create table idxpart21 partition of idxpart2 for values from (10) to (15);+create table idxpart22 partition of idxpart2 for values from (15) to (20);+create table idxpart3 (b int not null, a int not null);+alter table idxpart attach partition idxpart3 for values from (20, 20) to (30, 30);+select conname, contype, conrelid::regclass, conindid::regclass, conkey+ from pg_constraint where conrelid::regclass::text like 'idxpart%'+ order by conname;+ conname | contype | conrelid | conindid | conkey +----------------+---------+-----------+----------------+--------+ idxpart1_pkey | p | idxpart1 | idxpart1_pkey | {1,2}+ idxpart21_pkey | p | idxpart21 | idxpart21_pkey | {1,2}+ idxpart22_pkey | p | idxpart22 | idxpart22_pkey | {1,2}+ idxpart2_pkey | p | idxpart2 | idxpart2_pkey | {1,2}+ idxpart3_pkey | p | idxpart3 | idxpart3_pkey | {2,1}+ idxpart_pkey | p | idxpart | idxpart_pkey | {1,2}+(6 rows)++drop table idxpart;+create table idxpart (a int primary key, b int) partition by range (a);+create table idxpart1 partition of idxpart for values from (1) to (10);+\d idxpart1+ Table "public.idxpart1"+ Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+---------+ a | integer | | not null | + b | integer | | | +Partition of: idxpart FOR VALUES FROM (1) TO (10)+Indexes:+ "idxpart1_pkey" PRIMARY KEY, btree (a)++drop table idxpart;diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sqlindex 02a33ca7c4..115ed1efad 100644--- a/src/test/regress/sql/alter_table.sql+++ b/src/test/regress/sql/alter_table.sql@@ -2000,8 +2000,6 @@ CREATE TABLE partitioned (
a int,
b int
) PARTITION BY RANGE (a, (a+b+1));
-ALTER TABLE partitioned ADD UNIQUE (a);-ALTER TABLE partitioned ADD PRIMARY KEY (a);
ALTER TABLE partitioned ADD FOREIGN KEY (a) REFERENCES blah;
ALTER TABLE partitioned ADD EXCLUDE USING gist (a WITH &&);
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sqlindex b77b476436..673db6b77d 100644--- a/src/test/regress/sql/create_table.sql+++ b/src/test/regress/sql/create_table.sql@@ -294,10 +294,6 @@ CREATE TABLE partitioned (
) PARTITION BY LIST (a1, a2); -- fail
-- unsupported constraint type for partitioned tables
-CREATE TABLE partitioned (- a int PRIMARY KEY-) PARTITION BY RANGE (a);-
CREATE TABLE pkrel (
a int PRIMARY KEY
);
@@ -307,10 +303,6 @@ CREATE TABLE partitioned (
DROP TABLE pkrel;
CREATE TABLE partitioned (
- a int UNIQUE-) PARTITION BY RANGE (a);--CREATE TABLE partitioned (
a int,
EXCLUDE USING gist (a WITH &&)
) PARTITION BY RANGE (a);
diff --git a/src/test/regress/sql/indexing.sql b/src/test/regress/sql/indexing.sqlindex 53a28f120f..0831837560 100644--- a/src/test/regress/sql/indexing.sql+++ b/src/test/regress/sql/indexing.sql@@ -14,7 +14,6 @@ drop table idxpart;
-- Some unsupported cases
create table idxpart (a int, b int, c text) partition by range (a);
create table idxpart1 partition of idxpart for values from (0) to (10);
-create unique index on idxpart (a);
create index concurrently on idxpart (a);
drop table idxpart;
@@ -140,3 +139,74 @@ select attrelid::regclass, attname, attnum from pg_attribute
where attrelid in ('idxpart'::regclass, 'idxpart1'::regclass) and attnum > 0
order by attrelid::regclass, attnum;
drop table idxpart;
++--+-- Constraint-related indexes+--++-- Verify that it works to add primary key / unique to partitioned tables+create table idxpart (a int primary key, b int) partition by range (b);+create table idxpart (a int primary key, b int) partition by range (a);+\d idxpart+drop table idxpart;+create table idxpart (a int unique, b int) partition by range (a);+\d idxpart+drop table idxpart;+-- but not other types of index-based constraints+create table idxpart (a int, exclude (a with = )) partition by range (a);++-- It works to add primary keys after the partitioned table is created+create table idxpart (a int, b int) partition by range (a);+alter table idxpart add primary key (a);+\d idxpart+drop table idxpart;++-- It works to add unique constraints after the partitioned table is created+create table idxpart (a int, b int) partition by range (a);+alter table idxpart add unique (a);+\d idxpart+drop table idxpart;++-- Exclusion constraints cannot be added+create table idxpart (a int, b int) partition by range (a);+alter table idxpart add exclude (a with =);+drop table idxpart;++-- It is an error to add a constraint to columns that are not in the partition+-- key.+create table idxpart (a int, b int, primary key (a, b)) partition by range (a);+create table idxpart (a int, b int, primary key (a, b)) partition by range (b);+create table idxpart (a int, b int, unique (a, b)) partition by range (a);+create table idxpart (a int, b int, unique (a, b)) partition by range (b);+-- but using a partial set of columns is okay+create table idxpart (a int, b int, c int primary key) partition by range (b);+drop table idxpart;+create table idxpart (a int, b int, primary key (a)) partition by range (a, b);+drop table idxpart;+create table idxpart (a int, b int, primary key (b)) partition by range (a, b);+drop table idxpart;+create table idxpart (a int, b int, c int unique) partition by range (b);+drop table idxpart;+create table idxpart (a int, b int, unique (a)) partition by range (a, b);+drop table idxpart;+create table idxpart (a int, b int, unique (b)) partition by range (a, b);+drop table idxpart;++-- When (sub)partitions are created, they also contain the constraint+create table idxpart (a int, b int, primary key (a, b)) partition by range (a, b);+create table idxpart1 partition of idxpart for values from (1, 1) to (10, 10);+create table idxpart2 partition of idxpart for values from (10, 10) to (20, 20)+ partition by range (b);+create table idxpart21 partition of idxpart2 for values from (10) to (15);+create table idxpart22 partition of idxpart2 for values from (15) to (20);+create table idxpart3 (b int not null, a int not null);+alter table idxpart attach partition idxpart3 for values from (20, 20) to (30, 30);+select conname, contype, conrelid::regclass, conindid::regclass, conkey+ from pg_constraint where conrelid::regclass::text like 'idxpart%'+ order by conname;+drop table idxpart;++create table idxpart (a int primary key, b int) partition by range (a);+create table idxpart1 partition of idxpart for values from (1) to (10);+\d idxpart1+drop table idxpart;--
2.11.0
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-hackers@postgresql.org
Cc: alvherre@alvh.no-ip.org, robertmhaas@gmail.com, milyutinma@gmail.com
Subject: Re: [HACKERS] Proposal: Local indexes for partitioned table
In-Reply-To: <20171113175535.bvlfra6lm5rnoja2@alvherre.pgsql>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox