aboutsummaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/access/common/reloptions.c1
-rw-r--r--src/backend/access/heap/heapam.c9
-rw-r--r--src/backend/access/index/indexam.c3
-rw-r--r--src/backend/bootstrap/bootparse.y2
-rw-r--r--src/backend/catalog/aclchk.c9
-rw-r--r--src/backend/catalog/dependency.c14
-rw-r--r--src/backend/catalog/heap.c1
-rw-r--r--src/backend/catalog/index.c203
-rw-r--r--src/backend/catalog/objectaddress.c5
-rw-r--r--src/backend/catalog/pg_depend.c13
-rw-r--r--src/backend/catalog/pg_inherits.c80
-rw-r--r--src/backend/catalog/toasting.c2
-rw-r--r--src/backend/commands/indexcmds.c397
-rw-r--r--src/backend/commands/tablecmds.c653
-rw-r--r--src/backend/nodes/copyfuncs.c1
-rw-r--r--src/backend/nodes/equalfuncs.c1
-rw-r--r--src/backend/nodes/outfuncs.c1
-rw-r--r--src/backend/parser/gram.y33
-rw-r--r--src/backend/parser/parse_utilcmd.c65
-rw-r--r--src/backend/tcop/utility.c22
-rw-r--r--src/backend/utils/adt/amutils.c3
-rw-r--r--src/backend/utils/adt/ruleutils.c17
-rw-r--r--src/backend/utils/cache/relcache.c39
23 files changed, 1429 insertions, 145 deletions
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 425bc5d06ea..274f7aa8e97 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -993,6 +993,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
options = view_reloptions(datum, false);
break;
case RELKIND_INDEX:
+ case RELKIND_PARTITIONED_INDEX:
options = index_reloptions(amoptions, datum, false);
break;
case RELKIND_FOREIGN_TABLE:
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index dbc8f2d6c71..be263850cd3 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -1293,7 +1293,8 @@ heap_open(Oid relationId, LOCKMODE lockmode)
r = relation_open(relationId, lockmode);
- if (r->rd_rel->relkind == RELKIND_INDEX)
+ if (r->rd_rel->relkind == RELKIND_INDEX ||
+ r->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an index",
@@ -1321,7 +1322,8 @@ heap_openrv(const RangeVar *relation, LOCKMODE lockmode)
r = relation_openrv(relation, lockmode);
- if (r->rd_rel->relkind == RELKIND_INDEX)
+ if (r->rd_rel->relkind == RELKIND_INDEX ||
+ r->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an index",
@@ -1353,7 +1355,8 @@ heap_openrv_extended(const RangeVar *relation, LOCKMODE lockmode,
if (r)
{
- if (r->rd_rel->relkind == RELKIND_INDEX)
+ if (r->rd_rel->relkind == RELKIND_INDEX ||
+ r->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an index",
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index 1b61cd95151..91247f0fa59 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -154,7 +154,8 @@ index_open(Oid relationId, LOCKMODE lockmode)
r = relation_open(relationId, lockmode);
- if (r->rd_rel->relkind != RELKIND_INDEX)
+ if (r->rd_rel->relkind != RELKIND_INDEX &&
+ r->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is not an index",
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 8c52846a92e..dfd53fa054a 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -321,6 +321,7 @@ Boot_DeclareIndexStmt:
DefineIndex(relationId,
stmt,
$4,
+ InvalidOid,
false,
false,
false,
@@ -365,6 +366,7 @@ Boot_DeclareUniqueIndexStmt:
DefineIndex(relationId,
stmt,
$5,
+ InvalidOid,
false,
false,
false,
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index fac80612b80..50a2e2681b6 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -1824,7 +1824,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",
@@ -5405,7 +5406,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 */
@@ -5690,7 +5692,8 @@ removeExtObjInitPriv(Oid objoid, Oid classoid)
pg_class_tuple = (Form_pg_class) GETSTRUCT(tuple);
/* Indexes don't have permissions */
- if (pg_class_tuple->relkind == RELKIND_INDEX)
+ if (pg_class_tuple->relkind == RELKIND_INDEX ||
+ pg_class_tuple->relkind == RELKIND_PARTITIONED_INDEX)
return;
/* Composite types don't have permissions either */
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 269111b4c16..be60270ea5f 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -582,6 +582,7 @@ findDependentObjects(const ObjectAddress *object,
/* FALL THRU */
case DEPENDENCY_INTERNAL:
+ case DEPENDENCY_INTERNAL_AUTO:
/*
* This object is part of the internal implementation of
@@ -633,6 +634,14 @@ findDependentObjects(const ObjectAddress *object,
* transform this deletion request into a delete of this
* owning object.
*
+ * For INTERNAL_AUTO dependencies, we don't enforce this;
+ * in other words, we don't follow the links back to the
+ * owning object.
+ */
+ if (foundDep->deptype == DEPENDENCY_INTERNAL_AUTO)
+ break;
+
+ /*
* First, release caller's lock on this object and get
* deletion lock on the owning object. (We must release
* caller's lock to avoid deadlock against a concurrent
@@ -675,6 +684,7 @@ findDependentObjects(const ObjectAddress *object,
/* And we're done here. */
systable_endscan(scan);
return;
+
case DEPENDENCY_PIN:
/*
@@ -762,6 +772,7 @@ findDependentObjects(const ObjectAddress *object,
case DEPENDENCY_AUTO_EXTENSION:
subflags = DEPFLAG_AUTO;
break;
+ case DEPENDENCY_INTERNAL_AUTO:
case DEPENDENCY_INTERNAL:
subflags = DEPFLAG_INTERNAL;
break;
@@ -1109,7 +1120,8 @@ doDeletion(const ObjectAddress *object, int flags)
{
char relKind = get_rel_relkind(object->objectId);
- if (relKind == RELKIND_INDEX)
+ if (relKind == RELKIND_INDEX ||
+ relKind == RELKIND_PARTITIONED_INDEX)
{
bool concurrent = ((flags & PERFORM_DELETION_CONCURRENTLY) != 0);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 089b7965f2a..99f4d59863c 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -294,6 +294,7 @@ heap_create(const char *relname,
case RELKIND_COMPOSITE_TYPE:
case RELKIND_FOREIGN_TABLE:
case RELKIND_PARTITIONED_TABLE:
+ case RELKIND_PARTITIONED_INDEX:
create_storage = false;
/*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 007b929a6fa..f0223416adf 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -41,6 +41,8 @@
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_constraint_fn.h"
+#include "catalog/pg_depend.h"
+#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_tablespace.h"
@@ -55,6 +57,7 @@
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "parser/parser.h"
+#include "rewrite/rewriteManip.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "storage/predicate.h"
@@ -110,6 +113,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,
@@ -117,7 +121,8 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
bool primary,
bool isexclusion,
bool immediate,
- bool isvalid);
+ bool isvalid,
+ bool isready);
static void index_update_stats(Relation rel,
bool hasindex, bool isprimary,
double reltuples);
@@ -563,6 +568,7 @@ AppendAttributeTuples(Relation indexRelation, int numatts)
static void
UpdateIndexRelation(Oid indexoid,
Oid heapoid,
+ Oid parentIndexOid,
IndexInfo *indexInfo,
Oid *collationOids,
Oid *classOids,
@@ -570,7 +576,8 @@ UpdateIndexRelation(Oid indexoid,
bool primary,
bool isexclusion,
bool immediate,
- bool isvalid)
+ bool isvalid,
+ bool isready)
{
int2vector *indkey;
oidvector *indcollation;
@@ -644,8 +651,7 @@ UpdateIndexRelation(Oid indexoid,
values[Anum_pg_index_indisclustered - 1] = BoolGetDatum(false);
values[Anum_pg_index_indisvalid - 1] = BoolGetDatum(isvalid);
values[Anum_pg_index_indcheckxmin - 1] = BoolGetDatum(false);
- /* we set isvalid and isready the same way */
- values[Anum_pg_index_indisready - 1] = BoolGetDatum(isvalid);
+ values[Anum_pg_index_indisready - 1] = BoolGetDatum(isready);
values[Anum_pg_index_indislive - 1] = BoolGetDatum(true);
values[Anum_pg_index_indisreplident - 1] = BoolGetDatum(false);
values[Anum_pg_index_indkey - 1] = PointerGetDatum(indkey);
@@ -682,6 +688,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
@@ -707,6 +715,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
@@ -718,6 +728,7 @@ Oid
index_create(Relation heapRelation,
const char *indexRelationName,
Oid indexRelationId,
+ Oid parentIndexRelid,
Oid relFileNode,
IndexInfo *indexInfo,
List *indexColNames,
@@ -743,12 +754,18 @@ index_create(Relation heapRelation,
int i;
char relpersistence;
bool isprimary = (flags & INDEX_CREATE_IS_PRIMARY) != 0;
+ bool invalid = (flags & INDEX_CREATE_INVALID) != 0;
bool concurrent = (flags & INDEX_CREATE_CONCURRENT) != 0;
+ bool partitioned = (flags & INDEX_CREATE_PARTITIONED) != 0;
+ char relkind;
/* constraint flags can only be set when a constraint is requested */
Assert((constr_flags == 0) ||
((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0));
+ /* partitioned indexes must never be "built" by themselves */
+ Assert(!partitioned || (flags & INDEX_CREATE_SKIP_BUILD));
+ relkind = partitioned ? RELKIND_PARTITIONED_INDEX : RELKIND_INDEX;
is_exclusion = (indexInfo->ii_ExclusionOps != NULL);
pg_class = heap_open(RelationRelationId, RowExclusiveLock);
@@ -866,9 +883,9 @@ index_create(Relation heapRelation,
}
/*
- * create the index relation's relcache entry and physical disk file. (If
- * we fail further down, it's the smgr's responsibility to remove the disk
- * file again.)
+ * create the index relation's relcache entry and, if necessary, the
+ * physical disk file. (If we fail further down, it's the smgr's
+ * responsibility to remove the disk file again, if any.)
*/
indexRelation = heap_create(indexRelationName,
namespaceId,
@@ -876,7 +893,7 @@ index_create(Relation heapRelation,
indexRelationId,
relFileNode,
indexTupDesc,
- RELKIND_INDEX,
+ relkind,
relpersistence,
shared_relation,
mapped_relation,
@@ -933,12 +950,18 @@ 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,
+ !concurrent && !invalid,
!concurrent);
+ /* update pg_inherits, if needed */
+ if (OidIsValid(parentIndexRelid))
+ StoreSingleInheritance(indexRelationId, parentIndexRelid, 1);
+
/*
* Register constraint and dependencies for the index.
*
@@ -990,6 +1013,9 @@ index_create(Relation heapRelation,
else
{
bool have_simple_col = false;
+ DependencyType deptype;
+
+ deptype = OidIsValid(parentIndexRelid) ? DEPENDENCY_INTERNAL_AUTO : DEPENDENCY_AUTO;
/* Create auto dependencies on simply-referenced columns */
for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
@@ -1000,7 +1026,7 @@ index_create(Relation heapRelation,
referenced.objectId = heapRelationId;
referenced.objectSubId = indexInfo->ii_KeyAttrNumbers[i];
- recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+ recordDependencyOn(&myself, &referenced, deptype);
have_simple_col = true;
}
@@ -1018,10 +1044,20 @@ index_create(Relation heapRelation,
referenced.objectId = heapRelationId;
referenced.objectSubId = 0;
- recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
+ recordDependencyOn(&myself, &referenced, deptype);
}
}
+ /* Store dependency on parent index, if any */
+ if (OidIsValid(parentIndexRelid))
+ {
+ referenced.classId = RelationRelationId;
+ referenced.objectId = parentIndexRelid;
+ referenced.objectSubId = 0;
+
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL_AUTO);
+ }
+
/* Store dependency on collations */
/* The default collation is pinned, so don't bother recording it */
for (i = 0; i < indexInfo->ii_NumIndexAttrs; i++)
@@ -1567,9 +1603,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
@@ -1614,6 +1651,11 @@ index_drop(Oid indexId, bool concurrent)
DeleteRelationTuple(indexId);
/*
+ * fix INHERITS relation
+ */
+ DeleteInheritsTuple(indexId, InvalidOid);
+
+ /*
* We are presently too lazy to attempt to compute the new correct value
* of relhasindex (the next VACUUM will fix it if necessary). So there is
* no need to update the pg_class tuple for the owning relation. But we
@@ -1706,12 +1748,120 @@ BuildIndexInfo(Relation index)
ii->ii_BrokenHotChain = false;
/* set up for possible use by index AM */
+ ii->ii_Am = index->rd_rel->relam;
ii->ii_AmCache = NULL;
ii->ii_Context = CurrentMemoryContext;
return ii;
}
+/*
+ * CompareIndexInfo
+ * Return whether the properties of two indexes (in different tables)
+ * indicate that they have the "same" definitions.
+ *
+ * Note: passing collations and opfamilies separately is a kludge. Adding
+ * them to IndexInfo may result in better coding here and elsewhere.
+ *
+ * Use convert_tuples_by_name_map(index2, index1) to build the attmap.
+ */
+bool
+CompareIndexInfo(IndexInfo *info1, IndexInfo *info2,
+ Oid *collations1, Oid *collations2,
+ Oid *opfamilies1, Oid *opfamilies2,
+ AttrNumber *attmap, int maplen)
+{
+ int i;
+
+ if (info1->ii_Unique != info2->ii_Unique)
+ return false;
+
+ /* indexes are only equivalent if they have the same access method */
+ if (info1->ii_Am != info2->ii_Am)
+ return false;
+
+ /* and same number of attributes */
+ if (info1->ii_NumIndexAttrs != info2->ii_NumIndexAttrs)
+ return false;
+
+ /*
+ * and columns match through the attribute map (actual attribute numbers
+ * might differ!) Note that this implies that index columns that are
+ * expressions appear in the same positions. We will next compare the
+ * expressions themselves.
+ */
+ for (i = 0; i < info1->ii_NumIndexAttrs; i++)
+ {
+ if (maplen < info2->ii_KeyAttrNumbers[i])
+ elog(ERROR, "incorrect attribute map");
+
+ if (attmap[info2->ii_KeyAttrNumbers[i] - 1] !=
+ info1->ii_KeyAttrNumbers[i])
+ return false;
+
+ if (collations1[i] != collations2[i])
+ return false;
+ if (opfamilies1[i] != opfamilies2[i])
+ return false;
+ }
+
+ /*
+ * For expression indexes: either both are expression indexes, or neither
+ * is; if they are, make sure the expressions match.
+ */
+ if ((info1->ii_Expressions != NIL) != (info2->ii_Expressions != NIL))
+ return false;
+ if (info1->ii_Expressions != NIL)
+ {
+ bool found_whole_row;
+ Node *mapped;
+
+ mapped = map_variable_attnos((Node *) info2->ii_Expressions,
+ 1, 0, attmap, maplen,
+ InvalidOid, &found_whole_row);
+ if (found_whole_row)
+ {
+ /*
+ * we could throw an error here, but seems out of scope for this
+ * routine.
+ */
+ return false;
+ }
+
+ if (!equal(info1->ii_Expressions, mapped))
+ return false;
+ }
+
+ /* Partial index predicates must be identical, if they exist */
+ if ((info1->ii_Predicate == NULL) != (info2->ii_Predicate == NULL))
+ return false;
+ if (info1->ii_Predicate != NULL)
+ {
+ bool found_whole_row;
+ Node *mapped;
+
+ mapped = map_variable_attnos((Node *) info2->ii_Predicate,
+ 1, 0, attmap, maplen,
+ InvalidOid, &found_whole_row);
+ if (found_whole_row)
+ {
+ /*
+ * we could throw an error here, but seems out of scope for this
+ * routine.
+ */
+ return false;
+ }
+ if (!equal(info1->ii_Predicate, mapped))
+ return false;
+ }
+
+ /* No support currently for comparing exclusion indexes. */
+ if (info1->ii_ExclusionOps != NULL || info2->ii_ExclusionOps != NULL)
+ return false;
+
+ return true;
+}
+
/* ----------------
* BuildSpeculativeIndexInfo
* Add extra state to IndexInfo record
@@ -1934,6 +2084,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;
@@ -3344,6 +3497,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.
*/
@@ -3542,6 +3703,22 @@ reindex_relation(Oid relid, int flags, int options)
*/
rel = heap_open(relid, ShareLock);
+ /*
+ * This may be useful when implemented someday; but that day is not today.
+ * For now, avoid erroring out when called in a multi-table context
+ * (REINDEX SCHEMA) and happen to come across a partitioned table. The
+ * partitions may be reindexed on their own anyway.
+ */
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("REINDEX of partitioned tables is not yet implemented, skipping \"%s\"",
+ RelationGetRelationName(rel))));
+ heap_close(rel, ShareLock);
+ return false;
+ }
+
toast_relid = rel->rd_rel->reltoastrelid;
/*
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index bc999ca3c43..7576606c1b2 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -1217,7 +1217,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",
@@ -3483,6 +3484,7 @@ getRelationDescription(StringInfo buffer, Oid relid)
relname);
break;
case RELKIND_INDEX:
+ case RELKIND_PARTITIONED_INDEX:
appendStringInfo(buffer, _("index %s"),
relname);
break;
@@ -3957,6 +3959,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId)
appendStringInfoString(buffer, "table");
break;
case RELKIND_INDEX:
+ case RELKIND_PARTITIONED_INDEX:
appendStringInfoString(buffer, "index");
break;
case RELKIND_SEQUENCE:
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index 9dfbe123b52..2ea05f350b3 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/pg_inherits.c b/src/backend/catalog/pg_inherits.c
index b32d677347f..5a5beb9273e 100644
--- a/src/backend/catalog/pg_inherits.c
+++ b/src/backend/catalog/pg_inherits.c
@@ -405,3 +405,83 @@ typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId)
return result;
}
+
+/*
+ * Create a single pg_inherits row with the given data
+ */
+void
+StoreSingleInheritance(Oid relationId, Oid parentOid, int32 seqNumber)
+{
+ Datum values[Natts_pg_inherits];
+ bool nulls[Natts_pg_inherits];
+ HeapTuple tuple;
+ Relation inhRelation;
+
+ inhRelation = heap_open(InheritsRelationId, RowExclusiveLock);
+
+ /*
+ * Make the pg_inherits entry
+ */
+ values[Anum_pg_inherits_inhrelid - 1] = ObjectIdGetDatum(relationId);
+ values[Anum_pg_inherits_inhparent - 1] = ObjectIdGetDatum(parentOid);
+ values[Anum_pg_inherits_inhseqno - 1] = Int32GetDatum(seqNumber);
+
+ memset(nulls, 0, sizeof(nulls));
+
+ tuple = heap_form_tuple(RelationGetDescr(inhRelation), values, nulls);
+
+ CatalogTupleInsert(inhRelation, tuple);
+
+ heap_freetuple(tuple);
+
+ heap_close(inhRelation, RowExclusiveLock);
+}
+
+/*
+ * DeleteInheritsTuple
+ *
+ * Delete pg_inherits tuples with the given inhrelid. inhparent may be given
+ * as InvalidOid, in which case all tuples matching inhrelid are deleted;
+ * otherwise only delete tuples with the specified inhparent.
+ *
+ * Returns whether at least one row was deleted.
+ */
+bool
+DeleteInheritsTuple(Oid inhrelid, Oid inhparent)
+{
+ bool found = false;
+ Relation catalogRelation;
+ ScanKeyData key;
+ SysScanDesc scan;
+ HeapTuple inheritsTuple;
+
+ /*
+ * Find pg_inherits entries by inhrelid.
+ */
+ catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
+ ScanKeyInit(&key,
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(inhrelid));
+ scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
+ true, NULL, 1, &key);
+
+ while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
+ {
+ Oid parent;
+
+ /* Compare inhparent if it was given, and do the actual deletion. */
+ parent = ((Form_pg_inherits) GETSTRUCT(inheritsTuple))->inhparent;
+ if (!OidIsValid(inhparent) || parent == inhparent)
+ {
+ CatalogTupleDelete(catalogRelation, &inheritsTuple->t_self);
+ found = true;
+ }
+ }
+
+ /* Done */
+ systable_endscan(scan);
+ heap_close(catalogRelation, RowExclusiveLock);
+
+ return found;
+}
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 0b4b5631a1b..cf37011b73f 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -315,6 +315,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
indexInfo->ii_ReadyForInserts = true;
indexInfo->ii_Concurrent = false;
indexInfo->ii_BrokenHotChain = false;
+ indexInfo->ii_Am = BTREE_AM_OID;
indexInfo->ii_AmCache = NULL;
indexInfo->ii_Context = CurrentMemoryContext;
@@ -328,6 +329,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
coloptions[1] = 0;
index_create(toast_rel, toast_idxname, toastIndexOid, InvalidOid,
+ InvalidOid,
indexInfo,
list_make2("chunk_id", "chunk_seq"),
BTREE_AM_OID,
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9e6ba920086..8118a39a7b1 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -23,7 +23,10 @@
#include "catalog/catalog.h"
#include "catalog/index.h"
#include "catalog/indexing.h"
+#include "catalog/partition.h"
#include "catalog/pg_am.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_tablespace.h"
@@ -35,6 +38,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"
@@ -42,6 +46,7 @@
#include "parser/parse_coerce.h"
#include "parser/parse_func.h"
#include "parser/parse_oper.h"
+#include "rewrite/rewriteManip.h"
#include "storage/lmgr.h"
#include "storage/proc.h"
#include "storage/procarray.h"
@@ -77,6 +82,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
@@ -183,6 +189,7 @@ CheckIndexCompatible(Oid oldId,
indexInfo->ii_ExclusionOps = NULL;
indexInfo->ii_ExclusionProcs = NULL;
indexInfo->ii_ExclusionStrats = NULL;
+ indexInfo->ii_Am = accessMethodId;
indexInfo->ii_AmCache = NULL;
indexInfo->ii_Context = CurrentMemoryContext;
typeObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid));
@@ -292,14 +299,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 +316,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 +339,7 @@ DefineIndex(Oid relationId,
IndexAmRoutine *amRoutine;
bool amcanorder;
amoptions_function amoptions;
+ bool partitioned;
Datum reloptions;
int16 *coloptions;
IndexInfo *indexInfo;
@@ -382,23 +392,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 (stmt->primary || stmt->isconstraint)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot create constraints on partitioned tables")));
}
/*
@@ -574,6 +617,7 @@ DefineIndex(Oid relationId,
indexInfo->ii_ReadyForInserts = !stmt->concurrent;
indexInfo->ii_Concurrent = stmt->concurrent;
indexInfo->ii_BrokenHotChain = false;
+ indexInfo->ii_Am = accessMethodId;
indexInfo->ii_AmCache = NULL;
indexInfo->ii_Context = CurrentMemoryContext;
@@ -665,19 +709,24 @@ 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;
+ if (partitioned && stmt->relation && !stmt->relation->inh)
+ flags |= INDEX_CREATE_INVALID;
if (stmt->deferrable)
constr_flags |= INDEX_CONSTR_CREATE_DEFERRABLE;
@@ -685,8 +734,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 +755,160 @@ DefineIndex(Oid relationId,
CreateComments(indexRelationId, RelationRelationId, 0,
stmt->idxcomment);
+ if (partitioned)
+ {
+ /*
+ * Unless caller specified to skip this step (via ONLY), process
+ * each partition to make sure they all contain a corresponding index.
+ *
+ * If we're called internally (no stmt->relation), recurse always.
+ */
+ if (!stmt->relation || stmt->relation->inh)
+ {
+ PartitionDesc partdesc = RelationGetPartitionDesc(rel);
+ int nparts = partdesc->nparts;
+ Oid *part_oids = palloc(sizeof(Oid) * nparts);
+ bool invalidate_parent = false;
+ TupleDesc parentDesc;
+ Oid *opfamOids;
+
+ memcpy(part_oids, partdesc->oids, sizeof(Oid) * nparts);
+
+ parentDesc = CreateTupleDescCopy(RelationGetDescr(rel));
+ opfamOids = palloc(sizeof(Oid) * numberOfAttributes);
+ for (i = 0; i < numberOfAttributes; i++)
+ opfamOids[i] = get_opclass_family(classObjectId[i]);
+
+ heap_close(rel, NoLock);
+
+ /*
+ * For each partition, scan all existing indexes; if one matches
+ * our index definition and is not already attached to some other
+ * parent index, attach it to the one we just created.
+ *
+ * If none matches, build a new index by calling ourselves
+ * recursively with the same options (except for the index name).
+ */
+ for (i = 0; i < nparts; i++)
+ {
+ Oid childRelid = part_oids[i];
+ Relation childrel;
+ List *childidxs;
+ ListCell *cell;
+ AttrNumber *attmap;
+ bool found = false;
+ int maplen;
+
+ childrel = heap_open(childRelid, lockmode);
+ childidxs = RelationGetIndexList(childrel);
+ attmap =
+ convert_tuples_by_name_map(RelationGetDescr(childrel),
+ parentDesc,
+ gettext_noop("could not convert row type"));
+ maplen = parentDesc->natts;
+
+
+ foreach(cell, childidxs)
+ {
+ Oid cldidxid = lfirst_oid(cell);
+ Relation cldidx;
+ IndexInfo *cldIdxInfo;
+
+ /* this index is already partition of another one */
+ if (has_superclass(cldidxid))
+ continue;
+
+ cldidx = index_open(cldidxid, lockmode);
+ cldIdxInfo = BuildIndexInfo(cldidx);
+ if (CompareIndexInfo(cldIdxInfo, indexInfo,
+ cldidx->rd_indcollation,
+ collationObjectId,
+ cldidx->rd_opfamily,
+ opfamOids,
+ attmap, maplen))
+ {
+ /*
+ * Found a match. Attach index to parent and we're
+ * done, but keep lock till commit.
+ */
+ IndexSetParentIndex(cldidx, indexRelationId);
+
+ if (!IndexIsValid(cldidx->rd_index))
+ invalidate_parent = true;
+
+ found = true;
+ index_close(cldidx, NoLock);
+ break;
+ }
+
+ index_close(cldidx, lockmode);
+ }
+
+ list_free(childidxs);
+ heap_close(childrel, NoLock);
+
+ /*
+ * If no matching index was found, create our own.
+ */
+ if (!found)
+ {
+ IndexStmt *childStmt = copyObject(stmt);
+ bool found_whole_row;
+
+ childStmt->whereClause =
+ map_variable_attnos(stmt->whereClause, 1, 0,
+ attmap, maplen,
+ InvalidOid, &found_whole_row);
+ if (found_whole_row)
+ elog(ERROR, "cannot convert whole-row table reference");
+
+ childStmt->idxname = NULL;
+ childStmt->relationId = childRelid;
+ DefineIndex(childRelid, childStmt,
+ InvalidOid, /* no predefined OID */
+ indexRelationId, /* this is our child */
+ false, check_rights, check_not_in_use,
+ false, quiet);
+ }
+
+ pfree(attmap);
+ }
+
+ /*
+ * The pg_index row we inserted for this index was marked
+ * indisvalid=true. But if we attached an existing index that
+ * is invalid, this is incorrect, so update our row to
+ * invalid too.
+ */
+ if (invalidate_parent)
+ {
+ Relation pg_index = heap_open(IndexRelationId, RowExclusiveLock);
+ HeapTuple tup,
+ newtup;
+
+ tup = SearchSysCache1(INDEXRELID,
+ ObjectIdGetDatum(indexRelationId));
+ if (!tup)
+ elog(ERROR, "cache lookup failed for index %u",
+ indexRelationId);
+ newtup = heap_copytuple(tup);
+ ((Form_pg_index) GETSTRUCT(newtup))->indisvalid = false;
+ CatalogTupleUpdate(pg_index, &tup->t_self, newtup);
+ ReleaseSysCache(tup);
+ heap_close(pg_index, RowExclusiveLock);
+ heap_freetuple(newtup);
+ }
+ }
+ else
+ heap_close(rel, NoLock);
+
+ /*
+ * Indexes on partitioned tables are not themselves built, so we're
+ * done here.
+ */
+ return address;
+ }
+
if (!stmt->concurrent)
{
/* Close the heap and we're done, in the non-concurrent case */
@@ -1765,7 +1968,7 @@ ChooseIndexColumnNames(List *indexElems)
* ReindexIndex
* Recreate a specific index.
*/
-Oid
+void
ReindexIndex(RangeVar *indexRelation, int options)
{
Oid indOid;
@@ -1788,12 +1991,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;
}
/*
@@ -1832,7 +2040,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)));
@@ -1976,6 +2185,12 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
/*
* Only regular tables and matviews can have indexes, so ignore any
* other kind of relation.
+ *
+ * It is tempting to also consider partitioned tables here, but that
+ * has the problem that if the children are in the same schema, they
+ * would be processed twice. Maybe we could have a separate list of
+ * partitioned tables, and expand that afterwards into relids,
+ * ignoring any duplicates.
*/
if (classtuple->relkind != RELKIND_RELATION &&
classtuple->relkind != RELKIND_MATVIEW)
@@ -2038,3 +2253,155 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
MemoryContextDelete(private_context);
}
+
+/*
+ * ReindexPartitionedIndex
+ * Reindex each child of the given partitioned index.
+ *
+ * Not yet implemented.
+ */
+static void
+ReindexPartitionedIndex(Relation parentIdx)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("REINDEX is not yet implemented for partitioned indexes")));
+}
+
+/*
+ * Insert or delete an appropriate pg_inherits tuple to make the given index
+ * be a partition of the indicated parent index.
+ *
+ * This also corrects the pg_depend information for the affected index.
+ */
+void
+IndexSetParentIndex(Relation partitionIdx, Oid parentOid)
+{
+ Relation pg_inherits;
+ ScanKeyData key[2];
+ SysScanDesc scan;
+ Oid partRelid = RelationGetRelid(partitionIdx);
+ HeapTuple tuple;
+ bool fix_dependencies;
+
+ /* Make sure this is an index */
+ Assert(partitionIdx->rd_rel->relkind == RELKIND_INDEX ||
+ partitionIdx->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
+
+ /*
+ * Scan pg_inherits for rows linking our index to some parent.
+ */
+ pg_inherits = relation_open(InheritsRelationId, RowExclusiveLock);
+ ScanKeyInit(&key[0],
+ Anum_pg_inherits_inhrelid,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(partRelid));
+ ScanKeyInit(&key[1],
+ Anum_pg_inherits_inhseqno,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(1));
+ scan = systable_beginscan(pg_inherits, InheritsRelidSeqnoIndexId, true,
+ NULL, 2, key);
+ tuple = systable_getnext(scan);
+
+ if (!HeapTupleIsValid(tuple))
+ {
+ if (parentOid == InvalidOid)
+ {
+ /*
+ * No pg_inherits row, and no parent wanted: nothing to do in
+ * this case.
+ */
+ fix_dependencies = false;
+ }
+ else
+ {
+ Datum values[Natts_pg_inherits];
+ bool isnull[Natts_pg_inherits];
+
+ /*
+ * No pg_inherits row exists, and we want a parent for this index,
+ * so insert it.
+ */
+ values[Anum_pg_inherits_inhrelid - 1] = ObjectIdGetDatum(partRelid);
+ values[Anum_pg_inherits_inhparent - 1] =
+ ObjectIdGetDatum(parentOid);
+ values[Anum_pg_inherits_inhseqno - 1] = Int32GetDatum(1);
+ memset(isnull, false, sizeof(isnull));
+
+ tuple = heap_form_tuple(RelationGetDescr(pg_inherits),
+ values, isnull);
+ CatalogTupleInsert(pg_inherits, tuple);
+
+ fix_dependencies = true;
+ }
+ }
+ else
+ {
+ Form_pg_inherits inhForm = (Form_pg_inherits) GETSTRUCT(tuple);
+
+ if (parentOid == InvalidOid)
+ {
+ /*
+ * There exists a pg_inherits row, which we want to clear; do so.
+ */
+ CatalogTupleDelete(pg_inherits, &tuple->t_self);
+ fix_dependencies = true;
+ }
+ else
+ {
+ /*
+ * A pg_inherits row exists. If it's the same we want, then we're
+ * good; if it differs, that amounts to a corrupt catalog and
+ * should not happen.
+ */
+ if (inhForm->inhparent != parentOid)
+ {
+ /* unexpected: we should not get called in this case */
+ elog(ERROR, "bogus pg_inherit row: inhrelid %u inhparent %u",
+ inhForm->inhrelid, inhForm->inhparent);
+ }
+
+ /* already in the right state */
+ fix_dependencies = false;
+ }
+ }
+
+ /* done with pg_inherits */
+ systable_endscan(scan);
+ relation_close(pg_inherits, RowExclusiveLock);
+
+ if (fix_dependencies)
+ {
+ ObjectAddress partIdx;
+
+ /*
+ * Insert/delete pg_depend rows. If setting a parent, add an
+ * INTERNAL_AUTO dependency to the parent index; if making standalone,
+ * remove all existing rows and put back the regular dependency on the
+ * table.
+ */
+ ObjectAddressSet(partIdx, RelationRelationId, partRelid);
+
+ if (OidIsValid(parentOid))
+ {
+ ObjectAddress parentIdx;
+
+ ObjectAddressSet(parentIdx, RelationRelationId, parentOid);
+ recordDependencyOn(&partIdx, &parentIdx, DEPENDENCY_INTERNAL_AUTO);
+ }
+ else
+ {
+ ObjectAddress partitionTbl;
+
+ ObjectAddressSet(partitionTbl, RelationRelationId,
+ partitionIdx->rd_index->indrelid);
+
+ deleteDependencyRecordsForClass(RelationRelationId, partRelid,
+ RelationRelationId,
+ DEPENDENCY_INTERNAL_AUTO);
+
+ recordDependencyOn(&partIdx, &partitionTbl, DEPENDENCY_AUTO);
+ }
+ }
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 59806349cc7..57ee112653f 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}
};
@@ -284,6 +290,7 @@ struct DropRelationCallbackState
#define ATT_INDEX 0x0008
#define ATT_COMPOSITE_TYPE 0x0010
#define ATT_FOREIGN_TABLE 0x0020
+#define ATT_PARTITIONED_INDEX 0x0040
/*
* Partition tables are expected to be dropped when the parent partitioned
@@ -475,11 +482,17 @@ static void CreateInheritance(Relation child_rel, Relation parent_rel);
static void RemoveInheritance(Relation child_rel, Relation parent_rel);
static ObjectAddress ATExecAttachPartition(List **wqueue, Relation rel,
PartitionCmd *cmd);
+static void AttachPartitionEnsureIndexes(Relation rel, Relation attachrel);
static void ValidatePartitionConstraints(List **wqueue, Relation scanrel,
List *scanrel_children,
List *partConstraint,
bool validate_default);
static ObjectAddress ATExecDetachPartition(Relation rel, RangeVar *name);
+static ObjectAddress ATExecAttachPartitionIdx(List **wqueue, Relation rel,
+ RangeVar *name);
+static void validatePartitionedIndex(Relation partedIdx, Relation partedTbl);
+static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
+ Relation partitionTbl);
/* ----------------------------------------------------------------
@@ -897,6 +910,53 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
StorePartitionKey(rel, strategy, partnatts, partattrs, partexprs,
partopclass, partcollation);
+
+ /* make it all visible */
+ CommandCounterIncrement();
+ }
+
+ /*
+ * If we're creating a partition, create now all the indexes defined in
+ * the parent. We can't do it earlier, because DefineIndex wants to know
+ * the partition key which we just stored.
+ */
+ if (stmt->partbound)
+ {
+ Oid parentId = linitial_oid(inheritOids);
+ Relation parent;
+ List *idxlist;
+ ListCell *cell;
+
+ /* Already have strong enough lock on the parent */
+ parent = heap_open(parentId, NoLock);
+ idxlist = RelationGetIndexList(parent);
+
+ /*
+ * For each index in the parent table, create one in the partition
+ */
+ foreach(cell, idxlist)
+ {
+ Relation idxRel = index_open(lfirst_oid(cell), AccessShareLock);
+ AttrNumber *attmap;
+ IndexStmt *idxstmt;
+
+ attmap = convert_tuples_by_name_map(RelationGetDescr(rel),
+ RelationGetDescr(parent),
+ gettext_noop("could not convert row type"));
+ idxstmt =
+ generateClonedIndexStmt(NULL, RelationGetRelid(rel), idxRel,
+ attmap, RelationGetDescr(rel)->natts);
+ DefineIndex(RelationGetRelid(rel),
+ idxstmt,
+ InvalidOid,
+ RelationGetRelid(idxRel),
+ false, false, false, false, false);
+
+ index_close(idxRel, AccessShareLock);
+ }
+
+ list_free(idxlist);
+ heap_close(parent, NoLock);
}
/*
@@ -1179,10 +1239,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. An equivalent problem
+ * exists with indexes.
*/
if (classform->relkind == RELKIND_PARTITIONED_TABLE)
expected_relkind = RELKIND_RELATION;
+ else if (classform->relkind == RELKIND_PARTITIONED_INDEX)
+ expected_relkind = RELKIND_INDEX;
else
expected_relkind = classform->relkind;
@@ -1210,7 +1273,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))
@@ -2396,27 +2460,11 @@ StoreCatalogInheritance1(Oid relationId, Oid parentOid,
int32 seqNumber, Relation inhRelation,
bool child_is_partition)
{
- TupleDesc desc = RelationGetDescr(inhRelation);
- Datum values[Natts_pg_inherits];
- bool nulls[Natts_pg_inherits];
ObjectAddress childobject,
parentobject;
- HeapTuple tuple;
-
- /*
- * Make the pg_inherits entry
- */
- values[Anum_pg_inherits_inhrelid - 1] = ObjectIdGetDatum(relationId);
- values[Anum_pg_inherits_inhparent - 1] = ObjectIdGetDatum(parentOid);
- values[Anum_pg_inherits_inhseqno - 1] = Int32GetDatum(seqNumber);
- memset(nulls, 0, sizeof(nulls));
-
- tuple = heap_form_tuple(desc, values, nulls);
-
- CatalogTupleInsert(inhRelation, tuple);
-
- heap_freetuple(tuple);
+ /* store the pg_inherits row */
+ StoreSingleInheritance(relationId, parentOid, seqNumber);
/*
* Store a dependency too
@@ -2540,6 +2588,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,
@@ -3019,7 +3068,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);
@@ -3073,6 +3123,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),
@@ -3764,6 +3815,10 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
pass = AT_PASS_MISC;
break;
case AT_AttachPartition:
+ ATSimplePermissions(rel, ATT_TABLE | ATT_PARTITIONED_INDEX);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
case AT_DetachPartition:
ATSimplePermissions(rel, ATT_TABLE);
/* No command-specific prep needed */
@@ -4112,9 +4167,15 @@ 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:
+ /* ATPrepCmd ensures it must be a table */
+ Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
ATExecDetachPartition(rel, ((PartitionCmd *) cmd->def)->name);
break;
default: /* oops */
@@ -4148,9 +4209,13 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
- /* Foreign tables have no storage, nor do partitioned tables. */
+ /*
+ * Foreign tables have no storage, nor do partitioned tables and
+ * indexes.
+ */
if (tab->relkind == RELKIND_FOREIGN_TABLE ||
- tab->relkind == RELKIND_PARTITIONED_TABLE)
+ tab->relkind == RELKIND_PARTITIONED_TABLE ||
+ tab->relkind == RELKIND_PARTITIONED_INDEX)
continue;
/*
@@ -4752,6 +4817,9 @@ ATSimplePermissions(Relation rel, int allowed_targets)
case RELKIND_INDEX:
actual_target = ATT_INDEX;
break;
+ case RELKIND_PARTITIONED_INDEX:
+ actual_target = ATT_PARTITIONED_INDEX;
+ break;
case RELKIND_COMPOSITE_TYPE:
actual_target = ATT_COMPOSITE_TYPE;
break;
@@ -6194,6 +6262,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,
@@ -6205,7 +6274,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")));
@@ -6283,7 +6354,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),
@@ -6736,6 +6808,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 */
@@ -9139,7 +9212,8 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
{
char relKind = get_rel_relkind(foundObject.objectId);
- if (relKind == RELKIND_INDEX)
+ if (relKind == RELKIND_INDEX ||
+ relKind == RELKIND_PARTITIONED_INDEX)
{
Assert(foundObject.objectSubId == 0);
if (!list_member_oid(tab->changedIndexOids, foundObject.objectId))
@@ -9982,6 +10056,15 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
newOwnerId = tuple_class->relowner;
}
break;
+ case RELKIND_PARTITIONED_INDEX:
+ if (recursing)
+ break;
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change owner of index \"%s\"",
+ NameStr(tuple_class->relname)),
+ errhint("Change the ownership of the index's table, instead.")));
+ break;
case RELKIND_SEQUENCE:
if (!recursing &&
tuple_class->relowner != newOwnerId)
@@ -10103,6 +10186,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);
@@ -10110,7 +10194,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);
/*
@@ -10119,6 +10204,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
* relation, as well as its toast table (if it has one).
*/
if (tuple_class->relkind == RELKIND_RELATION ||
+ tuple_class->relkind == RELKIND_PARTITIONED_TABLE ||
tuple_class->relkind == RELKIND_MATVIEW ||
tuple_class->relkind == RELKIND_TOASTVALUE)
{
@@ -10427,6 +10513,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:
@@ -10839,7 +10926,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;
@@ -11633,45 +11721,18 @@ RemoveInheritance(Relation child_rel, Relation parent_rel)
Relation catalogRelation;
SysScanDesc scan;
ScanKeyData key[3];
- HeapTuple inheritsTuple,
- attributeTuple,
+ HeapTuple attributeTuple,
constraintTuple;
List *connames;
- bool found = false;
+ bool found;
bool child_is_partition = false;
/* If parent_rel is a partitioned table, child_rel must be a partition */
if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
child_is_partition = true;
- /*
- * Find and destroy the pg_inherits entry linking the two, or error out if
- * there is none.
- */
- catalogRelation = heap_open(InheritsRelationId, RowExclusiveLock);
- ScanKeyInit(&key[0],
- Anum_pg_inherits_inhrelid,
- BTEqualStrategyNumber, F_OIDEQ,
- ObjectIdGetDatum(RelationGetRelid(child_rel)));
- scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId,
- true, NULL, 1, key);
-
- while (HeapTupleIsValid(inheritsTuple = systable_getnext(scan)))
- {
- Oid inhparent;
-
- inhparent = ((Form_pg_inherits) GETSTRUCT(inheritsTuple))->inhparent;
- if (inhparent == RelationGetRelid(parent_rel))
- {
- CatalogTupleDelete(catalogRelation, &inheritsTuple->t_self);
- found = true;
- break;
- }
- }
-
- systable_endscan(scan);
- heap_close(catalogRelation, RowExclusiveLock);
-
+ found = DeleteInheritsTuple(RelationGetRelid(child_rel),
+ RelationGetRelid(parent_rel));
if (!found)
{
if (child_is_partition)
@@ -13226,7 +13287,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),
@@ -13946,6 +14008,9 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
/* Update the pg_class entry. */
StorePartitionBound(attachrel, rel, cmd->bound);
+ /* Ensure there exists a correct set of indexes in the partition. */
+ AttachPartitionEnsureIndexes(rel, attachrel);
+
/*
* Generate partition constraint from the partition bound specification.
* If the parent itself is a partition, make sure to include its
@@ -14016,6 +14081,127 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd)
}
/*
+ * AttachPartitionEnsureIndexes
+ * subroutine for ATExecAttachPartition to create/match indexes
+ *
+ * Enforce the indexing rule for partitioned tables during ALTER TABLE / ATTACH
+ * PARTITION: every partition must have an index attached to each index on the
+ * partitioned table.
+ */
+static void
+AttachPartitionEnsureIndexes(Relation rel, Relation attachrel)
+{
+ List *idxes;
+ List *attachRelIdxs;
+ Relation *attachrelIdxRels;
+ IndexInfo **attachInfos;
+ int i;
+ ListCell *cell;
+ MemoryContext cxt;
+ MemoryContext oldcxt;
+
+ cxt = AllocSetContextCreate(CurrentMemoryContext,
+ "AttachPartitionEnsureIndexes",
+ ALLOCSET_DEFAULT_SIZES);
+ oldcxt = MemoryContextSwitchTo(cxt);
+
+ idxes = RelationGetIndexList(rel);
+ attachRelIdxs = RelationGetIndexList(attachrel);
+ attachrelIdxRels = palloc(sizeof(Relation) * list_length(attachRelIdxs));
+ attachInfos = palloc(sizeof(IndexInfo *) * list_length(attachRelIdxs));
+
+ /* Build arrays of all existing indexes and their IndexInfos */
+ i = 0;
+ foreach(cell, attachRelIdxs)
+ {
+ Oid cldIdxId = lfirst_oid(cell);
+
+ attachrelIdxRels[i] = index_open(cldIdxId, AccessShareLock);
+ attachInfos[i] = BuildIndexInfo(attachrelIdxRels[i]);
+ i++;
+ }
+
+ /*
+ * For each index on the partitioned table, find a matching one in the
+ * partition-to-be; if one is not found, create one.
+ */
+ foreach(cell, idxes)
+ {
+ Oid idx = lfirst_oid(cell);
+ Relation idxRel = index_open(idx, AccessShareLock);
+ IndexInfo *info;
+ AttrNumber *attmap;
+ bool found = false;
+
+ /*
+ * Ignore indexes in the partitioned table other than partitioned
+ * indexes.
+ */
+ if (idxRel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
+ {
+ index_close(idxRel, AccessShareLock);
+ continue;
+ }
+
+ /* construct an indexinfo to compare existing indexes against */
+ info = BuildIndexInfo(idxRel);
+ attmap = convert_tuples_by_name_map(RelationGetDescr(attachrel),
+ RelationGetDescr(rel),
+ gettext_noop("could not convert row type"));
+
+ /*
+ * Scan the list of existing indexes in the partition-to-be, and mark
+ * the first matching, unattached one we find, if any, as partition of
+ * the parent index. If we find one, we're done.
+ */
+ for (i = 0; i < list_length(attachRelIdxs); i++)
+ {
+ /* does this index have a parent? if so, can't use it */
+ if (has_superclass(RelationGetRelid(attachrelIdxRels[i])))
+ continue;
+
+ if (CompareIndexInfo(attachInfos[i], info,
+ attachrelIdxRels[i]->rd_indcollation,
+ idxRel->rd_indcollation,
+ attachrelIdxRels[i]->rd_opfamily,
+ idxRel->rd_opfamily,
+ attmap,
+ RelationGetDescr(rel)->natts))
+ {
+ /* bingo. */
+ IndexSetParentIndex(attachrelIdxRels[i], idx);
+ found = true;
+ break;
+ }
+ }
+
+ /*
+ * If no suitable index was found in the partition-to-be, create one
+ * now.
+ */
+ if (!found)
+ {
+ IndexStmt *stmt;
+
+ stmt = generateClonedIndexStmt(NULL, RelationGetRelid(attachrel),
+ idxRel, attmap,
+ RelationGetDescr(rel)->natts);
+ DefineIndex(RelationGetRelid(attachrel), stmt, InvalidOid,
+ RelationGetRelid(idxRel),
+ false, false, false, false, false);
+ }
+
+ index_close(idxRel, AccessShareLock);
+ }
+
+ /* Clean up. */
+ for (i = 0; i < list_length(attachRelIdxs); i++)
+ index_close(attachrelIdxRels[i], AccessShareLock);
+ MemoryContextSwitchTo(oldcxt);
+ MemoryContextDelete(cxt);
+}
+
+/*
* ALTER TABLE DETACH PARTITION
*
* Return the address of the relation that is no longer a partition of rel.
@@ -14033,6 +14219,8 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
new_repl[Natts_pg_class];
ObjectAddress address;
Oid defaultPartOid;
+ List *indexes;
+ ListCell *cell;
/*
* We must lock the default partition, because detaching this partition
@@ -14094,6 +14282,24 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
}
}
+ /* detach indexes too */
+ indexes = RelationGetIndexList(partRel);
+ foreach(cell, indexes)
+ {
+ Oid idxid = lfirst_oid(cell);
+ Relation idx;
+
+ if (!has_superclass(idxid))
+ continue;
+
+ Assert((IndexGetRelation(get_partition_parent(idxid), false) ==
+ RelationGetRelid(rel)));
+
+ idx = index_open(idxid, AccessExclusiveLock);
+ IndexSetParentIndex(idx, InvalidOid);
+ relation_close(idx, AccessExclusiveLock);
+ }
+
/*
* Invalidate the parent's relcache so that the partition is no longer
* included in its partition descriptor.
@@ -14107,3 +14313,328 @@ ATExecDetachPartition(Relation rel, RangeVar *name)
return address;
}
+
+/*
+ * Before acquiring lock on an index, acquire the same lock on the owning
+ * table.
+ */
+struct AttachIndexCallbackState
+{
+ Oid partitionOid;
+ Oid parentTblOid;
+ bool lockedParentTbl;
+};
+
+static void
+RangeVarCallbackForAttachIndex(const RangeVar *rv, Oid relOid, Oid oldRelOid,
+ void *arg)
+{
+ struct AttachIndexCallbackState *state;
+ Form_pg_class classform;
+ HeapTuple tuple;
+
+ state = (struct AttachIndexCallbackState *) arg;
+
+ if (!state->lockedParentTbl)
+ {
+ LockRelationOid(state->parentTblOid, AccessShareLock);
+ state->lockedParentTbl = true;
+ }
+
+ /*
+ * If we previously locked some other heap, and the name we're looking up
+ * no longer refers to an index on that relation, release the now-useless
+ * lock. XXX maybe we should do *after* we verify whether the index does
+ * not actually belong to the same relation ...
+ */
+ if (relOid != oldRelOid && OidIsValid(state->partitionOid))
+ {
+ UnlockRelationOid(state->partitionOid, AccessShareLock);
+ state->partitionOid = InvalidOid;
+ }
+
+ /* Didn't find a relation, so no need for locking or permission checks. */
+ if (!OidIsValid(relOid))
+ return;
+
+ tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid));
+ if (!HeapTupleIsValid(tuple))
+ return; /* concurrently dropped, so nothing to do */
+ classform = (Form_pg_class) GETSTRUCT(tuple);
+ if (classform->relkind != RELKIND_PARTITIONED_INDEX &&
+ classform->relkind != RELKIND_INDEX)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("\"%s\" is not an index", rv->relname)));
+ ReleaseSysCache(tuple);
+
+ /*
+ * Since we need only examine the heap's tupledesc, an access share lock
+ * on it (preventing any DDL) is sufficient.
+ */
+ state->partitionOid = IndexGetRelation(relOid, false);
+ LockRelationOid(state->partitionOid, AccessShareLock);
+}
+
+/*
+ * ALTER INDEX i1 ATTACH PARTITION i2
+ */
+static ObjectAddress
+ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
+{
+ Relation partIdx;
+ Relation partTbl;
+ Relation parentTbl;
+ ObjectAddress address;
+ Oid partIdxId;
+ Oid currParent;
+ struct AttachIndexCallbackState state;
+
+ /*
+ * We need to obtain lock on the index 'name' to modify it, but we also
+ * need to read its owning table's tuple descriptor -- so we need to lock
+ * both. To avoid deadlocks, obtain lock on the table before doing so on
+ * the index. Furthermore, we need to examine the parent table of the
+ * partition, so lock that one too.
+ */
+ state.partitionOid = InvalidOid;
+ state.parentTblOid = parentIdx->rd_index->indrelid;
+ state.lockedParentTbl = false;
+ partIdxId =
+ RangeVarGetRelidExtended(name, AccessExclusiveLock, false, false,
+ RangeVarCallbackForAttachIndex,
+ (void *) &state);
+ /* Not there? */
+ if (!OidIsValid(partIdxId))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("index \"%s\" does not exist", name->relname)));
+
+ /* no deadlock risk: RangeVarGetRelidExtended already acquired the lock */
+ partIdx = relation_open(partIdxId, AccessExclusiveLock);
+
+ /* we already hold locks on both tables, so this is safe: */
+ parentTbl = relation_open(parentIdx->rd_index->indrelid, AccessShareLock);
+ partTbl = relation_open(partIdx->rd_index->indrelid, NoLock);
+
+ ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partIdx));
+
+ /* Silently do nothing if already in the right state */
+ currParent = !has_superclass(partIdxId) ? InvalidOid :
+ get_partition_parent(partIdxId);
+ if (currParent != RelationGetRelid(parentIdx))
+ {
+ IndexInfo *childInfo;
+ IndexInfo *parentInfo;
+ AttrNumber *attmap;
+ bool found;
+ int i;
+ PartitionDesc partDesc;
+
+ /*
+ * If this partition already has an index attached, refuse the operation.
+ */
+ refuseDupeIndexAttach(parentIdx, partIdx, partTbl);
+
+ if (OidIsValid(currParent))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
+ RelationGetRelationName(partIdx),
+ RelationGetRelationName(parentIdx)),
+ errdetail("Index \"%s\" is already attached to another index.",
+ RelationGetRelationName(partIdx))));
+
+ /* Make sure it indexes a partition of the other index's table */
+ partDesc = RelationGetPartitionDesc(parentTbl);
+ found = false;
+ for (i = 0; i < partDesc->nparts; i++)
+ {
+ if (partDesc->oids[i] == state.partitionOid)
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ ereport(ERROR,
+ (errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
+ RelationGetRelationName(partIdx),
+ RelationGetRelationName(parentIdx)),
+ errdetail("Index \"%s\" is not an index on any partition of table \"%s\".",
+ RelationGetRelationName(partIdx),
+ RelationGetRelationName(parentTbl))));
+
+ /* Ensure the indexes are compatible */
+ childInfo = BuildIndexInfo(partIdx);
+ parentInfo = BuildIndexInfo(parentIdx);
+ attmap = convert_tuples_by_name_map(RelationGetDescr(partTbl),
+ RelationGetDescr(parentTbl),
+ gettext_noop("could not convert row type"));
+ if (!CompareIndexInfo(childInfo, parentInfo,
+ partIdx->rd_indcollation,
+ parentIdx->rd_indcollation,
+ partIdx->rd_opfamily,
+ parentIdx->rd_opfamily,
+ attmap,
+ RelationGetDescr(partTbl)->natts))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
+ RelationGetRelationName(partIdx),
+ RelationGetRelationName(parentIdx)),
+ errdetail("The index definitions do not match.")));
+
+ /* All good -- do it */
+ IndexSetParentIndex(partIdx, RelationGetRelid(parentIdx));
+ pfree(attmap);
+
+ CommandCounterIncrement();
+
+ validatePartitionedIndex(parentIdx, parentTbl);
+ }
+
+ relation_close(parentTbl, AccessShareLock);
+ /* keep these locks till commit */
+ relation_close(partTbl, NoLock);
+ relation_close(partIdx, NoLock);
+
+ return address;
+}
+
+/*
+ * Verify whether the given partition already contains an index attached
+ * to the given partitioned index. If so, raise an error.
+ */
+static void
+refuseDupeIndexAttach(Relation parentIdx, Relation partIdx, Relation partitionTbl)
+{
+ Relation pg_inherits;
+ ScanKeyData key;
+ HeapTuple tuple;
+ SysScanDesc scan;
+
+ pg_inherits = heap_open(InheritsRelationId, AccessShareLock);
+ ScanKeyInit(&key, Anum_pg_inherits_inhparent,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(parentIdx)));
+ scan = systable_beginscan(pg_inherits, InheritsParentIndexId, true,
+ NULL, 1, &key);
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_inherits inhForm;
+ Oid tab;
+
+ inhForm = (Form_pg_inherits) GETSTRUCT(tuple);
+ tab = IndexGetRelation(inhForm->inhrelid, false);
+ if (tab == RelationGetRelid(partitionTbl))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot attach index \"%s\" as a partition of index \"%s\"",
+ RelationGetRelationName(partIdx),
+ RelationGetRelationName(parentIdx)),
+ errdetail("Another index is already attached for partition \"%s\".",
+ RelationGetRelationName(partitionTbl))));
+ }
+
+ systable_endscan(scan);
+ heap_close(pg_inherits, AccessShareLock);
+}
+
+/*
+ * Verify whether the set of attached partition indexes to a parent index on
+ * a partitioned table is complete. If it is, mark the parent index valid.
+ *
+ * This should be called each time a partition index is attached.
+ */
+static void
+validatePartitionedIndex(Relation partedIdx, Relation partedTbl)
+{
+ Relation inheritsRel;
+ SysScanDesc scan;
+ ScanKeyData key;
+ int tuples = 0;
+ HeapTuple inhTup;
+ bool updated = false;
+
+ Assert(partedIdx->rd_rel->relkind == RELKIND_PARTITIONED_INDEX);
+
+ /*
+ * Scan pg_inherits for this parent index. Count each valid index we find
+ * (verifying the pg_index entry for each), and if we reach the total
+ * amount we expect, we can mark this parent index as valid.
+ */
+ inheritsRel = heap_open(InheritsRelationId, AccessShareLock);
+ ScanKeyInit(&key, Anum_pg_inherits_inhparent,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(RelationGetRelid(partedIdx)));
+ scan = systable_beginscan(inheritsRel, InheritsParentIndexId, true,
+ NULL, 1, &key);
+ while ((inhTup = systable_getnext(scan)) != NULL)
+ {
+ Form_pg_inherits inhForm = (Form_pg_inherits) GETSTRUCT(inhTup);
+ HeapTuple indTup;
+ Form_pg_index indexForm;
+
+ indTup = SearchSysCache1(INDEXRELID,
+ ObjectIdGetDatum(inhForm->inhrelid));
+ if (!indTup)
+ elog(ERROR, "cache lookup failed for index %u",
+ inhForm->inhrelid);
+ indexForm = (Form_pg_index) GETSTRUCT(indTup);
+ if (IndexIsValid(indexForm))
+ tuples += 1;
+ ReleaseSysCache(indTup);
+ }
+
+ /* Done with pg_inherits */
+ systable_endscan(scan);
+ heap_close(inheritsRel, AccessShareLock);
+
+ /*
+ * If we found as many inherited indexes as the partitioned table has
+ * partitions, we're good; update pg_index to set indisvalid.
+ */
+ if (tuples == RelationGetPartitionDesc(partedTbl)->nparts)
+ {
+ Relation idxRel;
+ HeapTuple newtup;
+
+ idxRel = heap_open(IndexRelationId, RowExclusiveLock);
+
+ newtup = heap_copytuple(partedIdx->rd_indextuple);
+ ((Form_pg_index) GETSTRUCT(newtup))->indisvalid = true;
+ updated = true;
+
+ CatalogTupleUpdate(idxRel, &partedIdx->rd_indextuple->t_self, newtup);
+
+ heap_close(idxRel, RowExclusiveLock);
+ }
+
+ /*
+ * If this index is in turn a partition of a larger index, validating it
+ * might cause the parent to become valid also. Try that.
+ */
+ if (updated &&
+ has_superclass(RelationGetRelid(partedIdx)))
+ {
+ Oid parentIdxId,
+ parentTblId;
+ Relation parentIdx,
+ parentTbl;
+
+ /* make sure we see the validation we just did */
+ CommandCounterIncrement();
+
+ parentIdxId = get_partition_parent(RelationGetRelid(partedIdx));
+ parentTblId = get_partition_parent(RelationGetRelid(partedTbl));
+ parentIdx = relation_open(parentIdxId, AccessExclusiveLock);
+ parentTbl = relation_open(parentTblId, AccessExclusiveLock);
+ Assert(!parentIdx->rd_index->indisvalid);
+
+ validatePartitionedIndex(parentIdx, parentTbl);
+
+ relation_close(parentIdx, AccessExclusiveLock);
+ relation_close(parentTbl, AccessExclusiveLock);
+ }
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index ddbbc79823e..65d8c77d7aa 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3379,6 +3379,7 @@ _copyIndexStmt(const IndexStmt *from)
COPY_STRING_FIELD(idxname);
COPY_NODE_FIELD(relation);
+ COPY_SCALAR_FIELD(relationId);
COPY_STRING_FIELD(accessMethod);
COPY_STRING_FIELD(tableSpace);
COPY_NODE_FIELD(indexParams);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 30ccc9c5ae7..0bd12e862e5 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1332,6 +1332,7 @@ _equalIndexStmt(const IndexStmt *a, const IndexStmt *b)
{
COMPARE_STRING_FIELD(idxname);
COMPARE_NODE_FIELD(relation);
+ COMPARE_SCALAR_FIELD(relationId);
COMPARE_STRING_FIELD(accessMethod);
COMPARE_STRING_FIELD(tableSpace);
COMPARE_NODE_FIELD(indexParams);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 5e72df137e7..b1cdfc36a62 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2650,6 +2650,7 @@ _outIndexStmt(StringInfo str, const IndexStmt *node)
WRITE_STRING_FIELD(idxname);
WRITE_NODE_FIELD(relation);
+ WRITE_OID_FIELD(relationId);
WRITE_STRING_FIELD(accessMethod);
WRITE_STRING_FIELD(tableSpace);
WRITE_NODE_FIELD(indexParams);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e42b7caff6b..93e67e8adcc 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
@@ -1891,6 +1891,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 =
@@ -2025,6 +2034,22 @@ 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_table_cmd:
/* ALTER TABLE <name> ADD <coldef> */
ADD_P columnDef
@@ -7330,7 +7355,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);
@@ -7338,6 +7363,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;
@@ -7356,7 +7382,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);
@@ -7364,6 +7390,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name
n->concurrent = $4;
n->idxname = $8;
n->relation = $10;
+ n->relationId = InvalidOid;
n->accessMethod = $11;
n->indexParams = $13;
n->options = $15;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 128f1679c6b..90bb356df85 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -118,9 +118,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);
@@ -1185,7 +1182,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 */
@@ -1263,10 +1261,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);
@@ -1287,6 +1287,9 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx,
Datum datum;
bool isnull;
+ Assert((heapRel == NULL && OidIsValid(heapRelid)) ||
+ (heapRel != NULL && !OidIsValid(heapRelid)));
+
/*
* Fetch pg_class tuple of source index. We can't use the copy in the
* relcache entry because it doesn't include optional fields.
@@ -1322,7 +1325,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);
@@ -3289,18 +3293,39 @@ transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd)
{
Relation parentRel = cxt->rel;
- /* the table must be partitioned */
- if (parentRel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
- errmsg("\"%s\" is not partitioned",
- RelationGetRelationName(parentRel))));
-
- /* transform the partition bound, if any */
- Assert(RelationGetPartitionKey(parentRel) != NULL);
- if (cmd->bound != NULL)
- cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,
- cmd->bound);
+ switch (parentRel->rd_rel->relkind)
+ {
+ case RELKIND_PARTITIONED_TABLE:
+ /* transform the partition bound, if any */
+ Assert(RelationGetPartitionKey(parentRel) != NULL);
+ if (cmd->bound != NULL)
+ cxt->partbound = transformPartitionBound(cxt->pstate, parentRel,
+ cmd->bound);
+ break;
+ case RELKIND_PARTITIONED_INDEX:
+ /* nothing to check */
+ Assert(cmd->bound == NULL);
+ break;
+ case RELKIND_RELATION:
+ /* the table must be partitioned */
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("table \"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+ break;
+ case RELKIND_INDEX:
+ /* the index must be partitioned */
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("index \"%s\" is not partitioned",
+ RelationGetRelationName(parentRel))));
+ break;
+ default:
+ /* parser shouldn't let this case through */
+ elog(ERROR, "\"%s\" is not a partitioned table or index",
+ RelationGetRelationName(parentRel));
+ break;
+ }
}
/*
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index ec98a612ec9..9cccc8d39de 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"
@@ -1300,6 +1301,7 @@ ProcessUtilitySlow(ParseState *pstate,
IndexStmt *stmt = (IndexStmt *) parsetree;
Oid relid;
LOCKMODE lockmode;
+ List *inheritors = NIL;
if (stmt->concurrent)
PreventTransactionChain(isTopLevel,
@@ -1322,6 +1324,23 @@ ProcessUtilitySlow(ParseState *pstate,
RangeVarCallbackOwnsRelation,
NULL);
+ /*
+ * CREATE INDEX on partitioned tables (but not regular
+ * inherited tables) recurses to partitions, so we must
+ * acquire locks early to avoid deadlocks.
+ */
+ if (stmt->relation->inh)
+ {
+ Relation rel;
+
+ /* already locked by RangeVarGetRelidExtended */
+ rel = heap_open(relid, NoLock);
+ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
+ inheritors = find_all_inheritors(relid, lockmode,
+ NULL);
+ heap_close(rel, NoLock);
+ }
+
/* Run parse analysis ... */
stmt = transformIndexStmt(relid, stmt, queryString);
@@ -1331,6 +1350,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 */
@@ -1346,6 +1366,8 @@ ProcessUtilitySlow(ParseState *pstate,
parsetree);
commandCollected = true;
EventTriggerAlterTableEnd();
+
+ list_free(inheritors);
}
break;
diff --git a/src/backend/utils/adt/amutils.c b/src/backend/utils/adt/amutils.c
index a6d8feea5b4..0f7ceb62eb5 100644
--- a/src/backend/utils/adt/amutils.c
+++ b/src/backend/utils/adt/amutils.c
@@ -183,7 +183,8 @@ indexam_property(FunctionCallInfo fcinfo,
if (!HeapTupleIsValid(tuple))
PG_RETURN_NULL();
rd_rel = (Form_pg_class) GETSTRUCT(tuple);
- if (rd_rel->relkind != RELKIND_INDEX)
+ if (rd_rel->relkind != RELKIND_INDEX &&
+ rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
{
ReleaseSysCache(tuple);
PG_RETURN_NULL();
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 9cdbb06add7..c5f5a1ca3f9 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -317,7 +317,7 @@ static void decompile_column_index_array(Datum column_index_array, Oid relId,
static char *pg_get_ruledef_worker(Oid ruleoid, int prettyFlags);
static char *pg_get_indexdef_worker(Oid indexrelid, int colno,
const Oid *excludeOps,
- bool attrsOnly, bool showTblSpc,
+ bool attrsOnly, bool showTblSpc, bool inherits,
int prettyFlags, bool missing_ok);
static char *pg_get_statisticsobj_worker(Oid statextid, bool missing_ok);
static char *pg_get_partkeydef_worker(Oid relid, int prettyFlags,
@@ -1086,7 +1086,7 @@ pg_get_indexdef(PG_FUNCTION_ARGS)
prettyFlags = PRETTYFLAG_INDENT;
- res = pg_get_indexdef_worker(indexrelid, 0, NULL, false, false,
+ res = pg_get_indexdef_worker(indexrelid, 0, NULL, false, false, false,
prettyFlags, true);
if (res == NULL)
@@ -1107,7 +1107,7 @@ pg_get_indexdef_ext(PG_FUNCTION_ARGS)
prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
res = pg_get_indexdef_worker(indexrelid, colno, NULL, colno != 0, false,
- prettyFlags, true);
+ false, prettyFlags, true);
if (res == NULL)
PG_RETURN_NULL();
@@ -1123,7 +1123,7 @@ pg_get_indexdef_ext(PG_FUNCTION_ARGS)
char *
pg_get_indexdef_string(Oid indexrelid)
{
- return pg_get_indexdef_worker(indexrelid, 0, NULL, false, true, 0, false);
+ return pg_get_indexdef_worker(indexrelid, 0, NULL, false, true, true, 0, false);
}
/* Internal version that just reports the column definitions */
@@ -1133,7 +1133,7 @@ pg_get_indexdef_columns(Oid indexrelid, bool pretty)
int prettyFlags;
prettyFlags = pretty ? PRETTYFLAG_PAREN | PRETTYFLAG_INDENT : PRETTYFLAG_INDENT;
- return pg_get_indexdef_worker(indexrelid, 0, NULL, true, false,
+ return pg_get_indexdef_worker(indexrelid, 0, NULL, true, false, false,
prettyFlags, false);
}
@@ -1146,7 +1146,7 @@ pg_get_indexdef_columns(Oid indexrelid, bool pretty)
static char *
pg_get_indexdef_worker(Oid indexrelid, int colno,
const Oid *excludeOps,
- bool attrsOnly, bool showTblSpc,
+ bool attrsOnly, bool showTblSpc, bool inherits,
int prettyFlags, bool missing_ok)
{
/* might want a separate isConstraint parameter later */
@@ -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
+ && !inherits ? "ONLY " : "",
generate_relation_name(indrelid, NIL),
quote_identifier(NameStr(amrec->amname)));
else /* currently, must be EXCLUDE constraint */
@@ -2148,6 +2150,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
operators,
false,
false,
+ false,
prettyFlags,
false));
break;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 00ba33bfb44..c081b88b733 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -430,18 +430,26 @@ static void
RelationParseRelOptions(Relation relation, HeapTuple tuple)
{
bytea *options;
+ amoptions_function amoptsfn;
relation->rd_options = NULL;
- /* Fall out if relkind should not have options */
+ /*
+ * Look up any AM-specific parse function; fall out if relkind should not
+ * have options.
+ */
switch (relation->rd_rel->relkind)
{
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
- case RELKIND_INDEX:
case RELKIND_VIEW:
case RELKIND_MATVIEW:
case RELKIND_PARTITIONED_TABLE:
+ amoptsfn = NULL;
+ break;
+ case RELKIND_INDEX:
+ case RELKIND_PARTITIONED_INDEX:
+ amoptsfn = relation->rd_amroutine->amoptions;
break;
default:
return;
@@ -452,10 +460,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
* we might not have any other for pg_class yet (consider executing this
* code for pg_class itself)
*/
- options = extractRelOptions(tuple,
- GetPgClassDescriptor(),
- relation->rd_rel->relkind == RELKIND_INDEX ?
- relation->rd_amroutine->amoptions : NULL);
+ options = extractRelOptions(tuple, GetPgClassDescriptor(), amoptsfn);
/*
* Copy parsed data into CacheMemoryContext. To guard against the
@@ -2053,7 +2058,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);
@@ -2167,7 +2173,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 */
@@ -2387,7 +2394,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())
@@ -2403,7 +2411,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)
{
@@ -5461,7 +5470,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;
@@ -5825,7 +5837,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 */