From 218dfb0053b0d47f59de8a63ed1a3abdbde14c19 Mon Sep 17 00:00:00 2001 From: David Zhang Date: Thu, 17 Nov 2022 12:20:34 -0800 Subject: [PATCH 1/4] support global unique index with non-partition key --- contrib/pageinspect/btreefuncs.c | 7 +++-- doc/src/sgml/ref/create_index.sgml | 13 +++++++++ src/backend/access/common/reloptions.c | 1 + src/backend/access/index/indexam.c | 1 + src/backend/access/table/table.c | 1 + src/backend/catalog/aclchk.c | 3 ++ src/backend/catalog/dependency.c | 1 + src/backend/catalog/heap.c | 4 ++- src/backend/catalog/index.c | 11 +++++-- src/backend/catalog/objectaddress.c | 4 +++ src/backend/catalog/pg_class.c | 2 ++ src/backend/commands/cluster.c | 6 ++-- src/backend/commands/indexcmds.c | 28 +++++++++++++++--- src/backend/commands/tablecmds.c | 36 ++++++++++++++++++++--- src/backend/optimizer/util/plancat.c | 3 +- src/backend/parser/gram.y | 13 +++++++-- src/backend/parser/parse_utilcmd.c | 1 + src/backend/utils/adt/amutils.c | 1 + src/backend/utils/cache/relcache.c | 17 ++++++++--- src/bin/psql/describe.c | 17 ++++++++++- src/include/catalog/index.h | 1 + src/include/catalog/pg_class.h | 2 ++ src/include/nodes/parsenodes.h | 1 + src/test/regress/expected/indexing.out | 40 ++++++++++++++++++++++++++ src/test/regress/sql/indexing.sql | 11 +++++++ 25 files changed, 199 insertions(+), 26 deletions(-) diff --git a/contrib/pageinspect/btreefuncs.c b/contrib/pageinspect/btreefuncs.c index 9375d55e14..fea5a3033b 100644 --- a/contrib/pageinspect/btreefuncs.c +++ b/contrib/pageinspect/btreefuncs.c @@ -51,6 +51,7 @@ PG_FUNCTION_INFO_V1(bt_page_stats); #define IS_BTREE(r) ((r)->rd_rel->relam == BTREE_AM_OID) #define DatumGetItemPointer(X) ((ItemPointer) DatumGetPointer(X)) #define ItemPointerGetDatum(X) PointerGetDatum(X) +#define IS_GLOBAL_INDEX(r) ((r)->rd_rel->relkind == RELKIND_GLOBAL_INDEX) /* note: BlockNumber is unsigned, hence can't be negative */ #define CHECK_RELATION_BLOCK_RANGE(rel, blkno) { \ @@ -205,7 +206,7 @@ bt_page_stats_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version) relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); rel = relation_openrv(relrv, AccessShareLock); - if (!IS_INDEX(rel) || !IS_BTREE(rel)) + if ((!IS_INDEX(rel) && !IS_GLOBAL_INDEX(rel)) || !IS_BTREE(rel)) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a %s index", @@ -473,7 +474,7 @@ bt_page_items_internal(PG_FUNCTION_ARGS, enum pageinspect_version ext_version) relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); rel = relation_openrv(relrv, AccessShareLock); - if (!IS_INDEX(rel) || !IS_BTREE(rel)) + if ((!IS_INDEX(rel) && !IS_GLOBAL_INDEX(rel)) || !IS_BTREE(rel)) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a %s index", @@ -709,7 +710,7 @@ bt_metap(PG_FUNCTION_ARGS) relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); rel = relation_openrv(relrv, AccessShareLock); - if (!IS_INDEX(rel) || !IS_BTREE(rel)) + if ((!IS_INDEX(rel) && !IS_GLOBAL_INDEX(rel)) || !IS_BTREE(rel)) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a %s index", diff --git a/doc/src/sgml/ref/create_index.sgml b/doc/src/sgml/ref/create_index.sgml index 40986aa502..5444c096e1 100644 --- a/doc/src/sgml/ref/create_index.sgml +++ b/doc/src/sgml/ref/create_index.sgml @@ -28,6 +28,7 @@ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] storage_parameter [= value] [, ... ] ) ] [ TABLESPACE tablespace_name ] [ WHERE predicate ] + [ GLOBAL ] @@ -380,6 +381,18 @@ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] + + GLOBAL + + + Used with UNIQUE to enable cross-partition + uniqueness check on a partitioned table. Attempts to insert or + update data which would result in duplicate entries in other + partitions as a whole will generate an error. + + + + diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 75b7344891..70ad7ffe8f 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -1410,6 +1410,7 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, options = view_reloptions(datum, false); break; case RELKIND_INDEX: + case RELKIND_GLOBAL_INDEX: case RELKIND_PARTITIONED_INDEX: options = index_reloptions(amoptions, datum, false); break; diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c index fe80b8b0ba..5bf36cc686 100644 --- a/src/backend/access/index/indexam.c +++ b/src/backend/access/index/indexam.c @@ -136,6 +136,7 @@ index_open(Oid relationId, LOCKMODE lockmode) r = relation_open(relationId, lockmode); if (r->rd_rel->relkind != RELKIND_INDEX && + r->rd_rel->relkind != RELKIND_GLOBAL_INDEX && r->rd_rel->relkind != RELKIND_PARTITIONED_INDEX) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), diff --git a/src/backend/access/table/table.c b/src/backend/access/table/table.c index 7e94232f01..0f9570c894 100644 --- a/src/backend/access/table/table.c +++ b/src/backend/access/table/table.c @@ -138,6 +138,7 @@ static inline void validate_relation_kind(Relation r) { if (r->rd_rel->relkind == RELKIND_INDEX || + r->rd_rel->relkind == RELKIND_GLOBAL_INDEX || r->rd_rel->relkind == RELKIND_PARTITIONED_INDEX || r->rd_rel->relkind == RELKIND_COMPOSITE_TYPE) ereport(ERROR, diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 42360d37ca..79e14ca356 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -1819,6 +1819,7 @@ ExecGrant_Relation(InternalGrant *istmt) /* Not sensible to grant on an index */ if (pg_class_tuple->relkind == RELKIND_INDEX || + pg_class_tuple->relkind == RELKIND_GLOBAL_INDEX || pg_class_tuple->relkind == RELKIND_PARTITIONED_INDEX) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -5950,6 +5951,7 @@ recordExtObjInitPriv(Oid objoid, Oid classoid) * restrictions in ALTER EXTENSION ADD, but let's check anyway.) */ if (pg_class_tuple->relkind == RELKIND_INDEX || + pg_class_tuple->relkind == RELKIND_GLOBAL_INDEX || pg_class_tuple->relkind == RELKIND_PARTITIONED_INDEX || pg_class_tuple->relkind == RELKIND_COMPOSITE_TYPE) { @@ -6244,6 +6246,7 @@ removeExtObjInitPriv(Oid objoid, Oid classoid) * restrictions in ALTER EXTENSION DROP, but let's check anyway.) */ if (pg_class_tuple->relkind == RELKIND_INDEX || + pg_class_tuple->relkind == RELKIND_GLOBAL_INDEX || pg_class_tuple->relkind == RELKIND_PARTITIONED_INDEX || pg_class_tuple->relkind == RELKIND_COMPOSITE_TYPE) { diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 7f3e64b5ae..5ec1f32efa 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -1399,6 +1399,7 @@ doDeletion(const ObjectAddress *object, int flags) char relKind = get_rel_relkind(object->objectId); if (relKind == RELKIND_INDEX || + relKind == RELKIND_GLOBAL_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 5b49cc5a09..54a81c11dc 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -316,7 +316,8 @@ heap_create(const char *relname, * user defined relation, not a system one. */ if (!allow_system_table_mods && - ((IsCatalogNamespace(relnamespace) && relkind != RELKIND_INDEX) || + ((IsCatalogNamespace(relnamespace) && + (relkind != RELKIND_INDEX && relkind != RELKIND_GLOBAL_INDEX)) || IsToastNamespace(relnamespace)) && IsNormalProcessingMode()) ereport(ERROR, @@ -1300,6 +1301,7 @@ heap_create_with_catalog(const char *relname, if (!(relkind == RELKIND_SEQUENCE || relkind == RELKIND_TOASTVALUE || relkind == RELKIND_INDEX || + relkind == RELKIND_GLOBAL_INDEX || relkind == RELKIND_PARTITIONED_INDEX)) { Oid new_array_oid; diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 61f1d3926a..46a2fb6cc7 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -731,6 +731,7 @@ index_create(Relation heapRelation, bool invalid = (flags & INDEX_CREATE_INVALID) != 0; bool concurrent = (flags & INDEX_CREATE_CONCURRENT) != 0; bool partitioned = (flags & INDEX_CREATE_PARTITIONED) != 0; + bool globalindex = (flags & INDEX_CREATE_GLOBAL) != 0; char relkind; TransactionId relfrozenxid; MultiXactId relminmxid; @@ -742,7 +743,10 @@ index_create(Relation heapRelation, /* partitioned indexes must never be "built" by themselves */ Assert(!partitioned || (flags & INDEX_CREATE_SKIP_BUILD)); - relkind = partitioned ? RELKIND_PARTITIONED_INDEX : RELKIND_INDEX; + if (globalindex) + relkind = partitioned ? RELKIND_PARTITIONED_INDEX : RELKIND_GLOBAL_INDEX; + else + relkind = partitioned ? RELKIND_PARTITIONED_INDEX : RELKIND_INDEX; is_exclusion = (indexInfo->ii_ExclusionOps != NULL); pg_class = table_open(RelationRelationId, RowExclusiveLock); @@ -918,7 +922,7 @@ index_create(Relation heapRelation, binary_upgrade_next_index_pg_class_oid = InvalidOid; /* Override the index relfilenumber */ - if ((relkind == RELKIND_INDEX) && + if ((relkind == RELKIND_INDEX || relkind == RELKIND_GLOBAL_INDEX) && (!RelFileNumberIsValid(binary_upgrade_next_index_pg_class_relfilenumber))) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -2883,7 +2887,8 @@ index_update_stats(Relation rel, BlockNumber relpages = RelationGetNumberOfBlocks(rel); BlockNumber relallvisible; - if (rd_rel->relkind != RELKIND_INDEX) + if (rd_rel->relkind != RELKIND_INDEX && + rd_rel->relkind != RELKIND_GLOBAL_INDEX) visibilitymap_count(rel, &relallvisible, NULL); else /* don't bother for indexes */ relallvisible = 0; diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index c7de7232b8..0bfab32b65 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -1389,6 +1389,7 @@ get_relation_by_qualified_name(ObjectType objtype, List *object, { case OBJECT_INDEX: if (relation->rd_rel->relkind != RELKIND_INDEX && + relation->rd_rel->relkind != RELKIND_GLOBAL_INDEX && relation->rd_rel->relkind != RELKIND_PARTITIONED_INDEX) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -4172,6 +4173,7 @@ getRelationDescription(StringInfo buffer, Oid relid, bool missing_ok) relname); break; case RELKIND_INDEX: + case RELKIND_GLOBAL_INDEX: case RELKIND_PARTITIONED_INDEX: appendStringInfo(buffer, _("index %s"), relname); @@ -4706,6 +4708,7 @@ getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId, appendStringInfoString(buffer, "table"); break; case RELKIND_INDEX: + case RELKIND_GLOBAL_INDEX: case RELKIND_PARTITIONED_INDEX: appendStringInfoString(buffer, "index"); break; @@ -6187,6 +6190,7 @@ get_relkind_objtype(char relkind) case RELKIND_PARTITIONED_TABLE: return OBJECT_TABLE; case RELKIND_INDEX: + case RELKIND_GLOBAL_INDEX: case RELKIND_PARTITIONED_INDEX: return OBJECT_INDEX; case RELKIND_SEQUENCE: diff --git a/src/backend/catalog/pg_class.c b/src/backend/catalog/pg_class.c index b696fa2afd..39f410906c 100644 --- a/src/backend/catalog/pg_class.c +++ b/src/backend/catalog/pg_class.c @@ -45,6 +45,8 @@ errdetail_relkind_not_supported(char relkind) return errdetail("This operation is not supported for partitioned tables."); case RELKIND_PARTITIONED_INDEX: return errdetail("This operation is not supported for partitioned indexes."); + case RELKIND_GLOBAL_INDEX: + return errdetail("This operation is not supported for global indexes."); default: elog(ERROR, "unrecognized relkind: '%c'", relkind); return 0; diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 1976a373ef..eff371ff12 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -1199,7 +1199,8 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class, */ /* set rel1's frozen Xid and minimum MultiXid */ - if (relform1->relkind != RELKIND_INDEX) + if (relform1->relkind != RELKIND_INDEX && + relform1->relkind != RELKIND_GLOBAL_INDEX) { Assert(!TransactionIdIsValid(frozenXid) || TransactionIdIsNormal(frozenXid)); @@ -1686,7 +1687,8 @@ get_tables_to_cluster_partitioned(MemoryContext cluster_context, Oid indexOid) RelToCluster *rtc; /* consider only leaf indexes */ - if (get_rel_relkind(indexrelid) != RELKIND_INDEX) + if (get_rel_relkind(indexrelid) != RELKIND_INDEX && + get_rel_relkind(indexrelid) != RELKIND_GLOBAL_INDEX) continue; /* Silently skip partitions which the user has no access to. */ diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 659e189549..099bf68e77 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -711,8 +711,20 @@ DefineIndex(Oid relationId, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot create exclusion constraints on partitioned table \"%s\"", RelationGetRelationName(rel)))); + + if (stmt->global_index && !stmt->unique) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create global index without unique on partitioned table \"%s\"", + RelationGetRelationName(rel)))); } + if (stmt->global_index && rel->rd_rel->relkind == RELKIND_RELATION && !rel->rd_rel->relispartition) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create global index on non-partitioned table \"%s\"", + RelationGetRelationName(rel)))); + /* * Don't try to CREATE INDEX on temp tables of other backends. */ @@ -923,7 +935,7 @@ DefineIndex(Oid relationId, * We could lift this limitation if we had global indexes, but those have * their own problems, so this is a useful feature combination. */ - if (partitioned && (stmt->unique || stmt->primary)) + if (partitioned && (stmt->unique || stmt->primary) && !stmt->global_index) { PartitionKey key = RelationGetPartitionKey(rel); const char *constraint_type; @@ -1026,7 +1038,7 @@ DefineIndex(Oid relationId, } } - if (!found) + if (!found && !stmt->global_index) { Form_pg_attribute att; @@ -1145,6 +1157,8 @@ DefineIndex(Oid relationId, if (pd->nparts != 0) flags |= INDEX_CREATE_INVALID; } + if (stmt->global_index) + flags |= INDEX_CREATE_GLOBAL; if (stmt->deferrable) constr_flags |= INDEX_CONSTR_CREATE_DEFERRABLE; @@ -2784,6 +2798,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation, if (!relkind) return; if (relkind != RELKIND_INDEX && + relkind != RELKIND_GLOBAL_INDEX && relkind != RELKIND_PARTITIONED_INDEX) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -3176,6 +3191,7 @@ ReindexPartitions(Oid relid, ReindexParams *params, bool isTopLevel) continue; Assert(partkind == RELKIND_INDEX || + partkind == RELKIND_GLOBAL_INDEX || partkind == RELKIND_RELATION); /* Save partition OID */ @@ -3268,7 +3284,8 @@ ReindexMultipleInternal(List *relids, ReindexParams *params) (void) ReindexRelationConcurrently(relid, &newparams); /* ReindexRelationConcurrently() does the verbose output */ } - else if (relkind == RELKIND_INDEX) + else if (relkind == RELKIND_INDEX || + relkind == RELKIND_GLOBAL_INDEX) { ReindexParams newparams = *params; @@ -3527,6 +3544,7 @@ ReindexRelationConcurrently(Oid relationOid, ReindexParams *params) break; } case RELKIND_INDEX: + case RELKIND_GLOBAL_INDEX: { Oid heapId = IndexGetRelation(relationOid, (params->options & REINDEXOPT_MISSING_OK) != 0); @@ -4127,7 +4145,8 @@ ReindexRelationConcurrently(Oid relationOid, ReindexParams *params) /* Log what we did */ if ((params->options & REINDEXOPT_VERBOSE) != 0) { - if (relkind == RELKIND_INDEX) + if (relkind == RELKIND_INDEX || + relkind == RELKIND_GLOBAL_INDEX) ereport(INFO, (errmsg("index \"%s.%s\" was reindexed", relationNamespace, relationName), @@ -4180,6 +4199,7 @@ IndexSetParentIndex(Relation partitionIdx, Oid parentOid) /* Make sure this is an index */ Assert(partitionIdx->rd_rel->relkind == RELKIND_INDEX || + partitionIdx->rd_rel->relkind == RELKIND_GLOBAL_INDEX || partitionIdx->rd_rel->relkind == RELKIND_PARTITIONED_INDEX); /* diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 6007e10730..b16a1180d8 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -294,6 +294,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = { gettext_noop("index \"%s\" does not exist, skipping"), gettext_noop("\"%s\" is not an index"), gettext_noop("Use DROP INDEX to remove an index.")}, + {RELKIND_GLOBAL_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} }; @@ -320,6 +326,7 @@ struct DropRelationCallbackState #define ATT_FOREIGN_TABLE 0x0020 #define ATT_PARTITIONED_INDEX 0x0040 #define ATT_SEQUENCE 0x0080 +#define ATT_GLOBAL_INDEX 0x0100 /* * ForeignTruncateInfo @@ -1564,6 +1571,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid, expected_relkind = RELKIND_RELATION; else if (classform->relkind == RELKIND_PARTITIONED_INDEX) expected_relkind = RELKIND_INDEX; + else if (classform->relkind == RELKIND_GLOBAL_INDEX) + expected_relkind = RELKIND_GLOBAL_INDEX; else expected_relkind = classform->relkind; @@ -1584,7 +1593,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid, * only concerns indexes of toast relations that became invalid during a * REINDEX CONCURRENTLY process. */ - if (IsSystemClass(relOid, classform) && classform->relkind == RELKIND_INDEX) + if (IsSystemClass(relOid, classform) && (classform->relkind == RELKIND_INDEX || + classform->relkind == RELKIND_GLOBAL_INDEX)) { HeapTuple locTuple; Form_pg_index indexform; @@ -1623,7 +1633,8 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid, * entry, though --- the relation may have been dropped. Note that this * code will execute for either plain or partitioned indexes. */ - if (expected_relkind == RELKIND_INDEX && + if ((expected_relkind == RELKIND_INDEX || + expected_relkind == RELKIND_GLOBAL_INDEX) && relOid != oldRelOid) { state->heapOid = IndexGetRelation(relOid, true); @@ -3406,6 +3417,7 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing) relkind != RELKIND_MATVIEW && relkind != RELKIND_COMPOSITE_TYPE && relkind != RELKIND_INDEX && + relkind != RELKIND_GLOBAL_INDEX && relkind != RELKIND_PARTITIONED_INDEX && relkind != RELKIND_FOREIGN_TABLE && relkind != RELKIND_PARTITIONED_TABLE) @@ -3837,6 +3849,7 @@ RenameRelation(RenameStmt *stmt) */ relkind = get_rel_relkind(relid); obj_is_index = (relkind == RELKIND_INDEX || + relkind == RELKIND_GLOBAL_INDEX || relkind == RELKIND_PARTITIONED_INDEX); if (obj_is_index || is_index_stmt == obj_is_index) break; @@ -3902,6 +3915,7 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo */ Assert(!is_index || is_index == (targetrelation->rd_rel->relkind == RELKIND_INDEX || + targetrelation->rd_rel->relkind == RELKIND_GLOBAL_INDEX || targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)); /* @@ -3929,6 +3943,7 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo * Also rename the associated constraint, if any. */ if (targetrelation->rd_rel->relkind == RELKIND_INDEX || + targetrelation->rd_rel->relkind == RELKIND_GLOBAL_INDEX || targetrelation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) { Oid constraintId = get_index_constraint(myrelid); @@ -4013,6 +4028,7 @@ CheckTableNotInUse(Relation rel, const char *stmt) stmt, RelationGetRelationName(rel)))); if (rel->rd_rel->relkind != RELKIND_INDEX && + rel->rd_rel->relkind != RELKIND_GLOBAL_INDEX && rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX && AfterTriggerPendingOnRel(RelationGetRelid(rel))) ereport(ERROR, @@ -6271,6 +6287,9 @@ ATSimplePermissions(AlterTableType cmdtype, Relation rel, int allowed_targets) case RELKIND_INDEX: actual_target = ATT_INDEX; break; + case RELKIND_GLOBAL_INDEX: + actual_target = ATT_GLOBAL_INDEX; + break; case RELKIND_PARTITIONED_INDEX: actual_target = ATT_PARTITIONED_INDEX; break; @@ -8061,6 +8080,7 @@ ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newVa * column numbers could contain gaps if columns are later dropped. */ if (rel->rd_rel->relkind != RELKIND_INDEX && + rel->rd_rel->relkind != RELKIND_GLOBAL_INDEX && rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX && !colName) ereport(ERROR, @@ -8122,6 +8142,7 @@ ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, Node *newVa colName))); if (rel->rd_rel->relkind == RELKIND_INDEX || + rel->rd_rel->relkind == RELKIND_GLOBAL_INDEX || rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) { if (attnum > rel->rd_index->indnkeyatts) @@ -13736,6 +13757,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock /* ok to change owner */ break; case RELKIND_INDEX: + case RELKIND_GLOBAL_INDEX: if (!recursing) { /* @@ -13886,6 +13908,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_GLOBAL_INDEX && tuple_class->relkind != RELKIND_PARTITIONED_INDEX && tuple_class->relkind != RELKIND_TOASTVALUE) changeDependencyOnOwner(RelationRelationId, relationOid, @@ -14232,6 +14255,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, (void) view_reloptions(newOptions, true); break; case RELKIND_INDEX: + case RELKIND_GLOBAL_INDEX: case RELKIND_PARTITIONED_INDEX: (void) index_reloptions(rel->rd_indam->amoptions, newOptions, true); break; @@ -14419,7 +14443,8 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode) newrlocator.spcOid = newTableSpace; /* hand off to AM to actually create new rel storage and copy the data */ - if (rel->rd_rel->relkind == RELKIND_INDEX) + if (rel->rd_rel->relkind == RELKIND_INDEX || + rel->rd_rel->relkind == RELKIND_GLOBAL_INDEX) { index_copy_data(rel, newrlocator); } @@ -14602,6 +14627,7 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt) relForm->relkind != RELKIND_PARTITIONED_TABLE) || (stmt->objtype == OBJECT_INDEX && relForm->relkind != RELKIND_INDEX && + relForm->relkind != RELKIND_GLOBAL_INDEX && relForm->relkind != RELKIND_PARTITIONED_INDEX) || (stmt->objtype == OBJECT_MATVIEW && relForm->relkind != RELKIND_MATVIEW)) @@ -17103,6 +17129,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid, errmsg("\"%s\" is not a composite type", rv->relname))); if (reltype == OBJECT_INDEX && relkind != RELKIND_INDEX && + relkind != RELKIND_GLOBAL_INDEX && relkind != RELKIND_PARTITIONED_INDEX && !IsA(stmt, RenameStmt)) ereport(ERROR, @@ -17125,7 +17152,7 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid, */ if (IsA(stmt, AlterObjectSchemaStmt)) { - if (relkind == RELKIND_INDEX || relkind == RELKIND_PARTITIONED_INDEX) + if (relkind == RELKIND_INDEX || relkind == RELKIND_GLOBAL_INDEX || relkind == RELKIND_PARTITIONED_INDEX) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot change schema of index \"%s\"", @@ -18870,6 +18897,7 @@ RangeVarCallbackForAttachIndex(const RangeVar *rv, Oid relOid, Oid oldRelOid, return; /* concurrently dropped, so nothing to do */ classform = (Form_pg_class) GETSTRUCT(tuple); if (classform->relkind != RELKIND_PARTITIONED_INDEX && + classform->relkind != RELKIND_GLOBAL_INDEX && classform->relkind != RELKIND_INDEX) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 9defe37836..5417cafca1 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -990,7 +990,8 @@ estimate_rel_size(Relation rel, int32 *attr_widths, table_relation_estimate_size(rel, attr_widths, pages, tuples, allvisfrac); } - else if (rel->rd_rel->relkind == RELKIND_INDEX) + else if (rel->rd_rel->relkind == RELKIND_INDEX || + rel->rd_rel->relkind == RELKIND_GLOBAL_INDEX) { /* * XXX: It'd probably be good to move this into a callback, individual diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 2dddd8f302..a259079e8c 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -491,7 +491,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type unicode_normal_form %type opt_instead -%type opt_unique opt_verbose opt_full +%type opt_unique opt_verbose opt_full opt_global %type opt_freeze opt_analyze opt_default opt_recheck %type opt_binary copy_delimiter @@ -7918,7 +7918,7 @@ defacl_privilege_target: IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_single_name ON relation_expr access_method_clause '(' index_params ')' - opt_include opt_unique_null_treatment opt_reloptions OptTableSpace where_clause + opt_include opt_unique_null_treatment opt_reloptions OptTableSpace where_clause opt_global { IndexStmt *n = makeNode(IndexStmt); @@ -7933,6 +7933,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_single_name n->options = $14; n->tableSpace = $15; n->whereClause = $16; + n->global_index = $17; n->excludeOpNames = NIL; n->idxcomment = NULL; n->indexOid = InvalidOid; @@ -7950,7 +7951,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_single_name } | CREATE opt_unique INDEX opt_concurrently IF_P NOT EXISTS name ON relation_expr access_method_clause '(' index_params ')' - opt_include opt_unique_null_treatment opt_reloptions OptTableSpace where_clause + opt_include opt_unique_null_treatment opt_reloptions OptTableSpace where_clause opt_global { IndexStmt *n = makeNode(IndexStmt); @@ -7965,6 +7966,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_single_name n->options = $17; n->tableSpace = $18; n->whereClause = $19; + n->global_index = $20; n->excludeOpNames = NIL; n->idxcomment = NULL; n->indexOid = InvalidOid; @@ -7982,6 +7984,11 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_single_name } ; +opt_global: + GLOBAL { $$ = true; } + | /*EMPTY*/ { $$ = false; } + ; + opt_unique: UNIQUE { $$ = true; } | /*EMPTY*/ { $$ = false; } diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 8140e79d8f..358e7df040 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3982,6 +3982,7 @@ transformPartitionCmd(CreateStmtContext *cxt, PartitionCmd *cmd) RelationGetRelationName(parentRel)))); break; case RELKIND_INDEX: + case RELKIND_GLOBAL_INDEX: /* the index must be partitioned */ ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), diff --git a/src/backend/utils/adt/amutils.c b/src/backend/utils/adt/amutils.c index 60fd396f24..faf810da2f 100644 --- a/src/backend/utils/adt/amutils.c +++ b/src/backend/utils/adt/amutils.c @@ -176,6 +176,7 @@ indexam_property(FunctionCallInfo fcinfo, PG_RETURN_NULL(); rd_rel = (Form_pg_class) GETSTRUCT(tuple); if (rd_rel->relkind != RELKIND_INDEX && + rd_rel->relkind != RELKIND_GLOBAL_INDEX && rd_rel->relkind != RELKIND_PARTITIONED_INDEX) { ReleaseSysCache(tuple); diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index bd6cd4e47b..151df2920c 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -480,6 +480,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple) amoptsfn = NULL; break; case RELKIND_INDEX: + case RELKIND_GLOBAL_INDEX: case RELKIND_PARTITIONED_INDEX: amoptsfn = relation->rd_indam->amoptions; break; @@ -1202,6 +1203,7 @@ retry: * initialize access method information */ if (relation->rd_rel->relkind == RELKIND_INDEX || + relation->rd_rel->relkind == RELKIND_GLOBAL_INDEX || relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) RelationInitIndexAccessInfo(relation); else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind) || @@ -2082,6 +2084,7 @@ RelationIdGetRelation(Oid relationId) * a headache for indexes that reload itself depends on. */ if (rd->rd_rel->relkind == RELKIND_INDEX || + rd->rd_rel->relkind == RELKIND_GLOBAL_INDEX || rd->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) RelationReloadIndexInfo(rd); else @@ -2222,6 +2225,7 @@ RelationReloadIndexInfo(Relation relation) /* Should be called only for invalidated, live indexes */ Assert((relation->rd_rel->relkind == RELKIND_INDEX || + relation->rd_rel->relkind == RELKIND_GLOBAL_INDEX || relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) && !relation->rd_isvalid && relation->rd_droppedSubid == InvalidSubTransactionId); @@ -2349,7 +2353,8 @@ RelationReloadNailed(Relation relation) if (!IsTransactionState() || relation->rd_refcnt <= 1) return; - if (relation->rd_rel->relkind == RELKIND_INDEX) + if (relation->rd_rel->relkind == RELKIND_INDEX || + relation->rd_rel->relkind == RELKIND_GLOBAL_INDEX) { /* * If it's a nailed-but-not-mapped index, then we need to re-read the @@ -2542,6 +2547,7 @@ RelationClearRelation(Relation relation, bool rebuild) * index, and we check for pg_index updates too. */ if ((relation->rd_rel->relkind == RELKIND_INDEX || + relation->rd_rel->relkind == RELKIND_GLOBAL_INDEX || relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX) && relation->rd_refcnt > 0 && relation->rd_indexcxt != NULL) @@ -3714,7 +3720,8 @@ RelationSetNewRelfilenumber(Relation relation, char persistence) newrelfilenumber = GetNewRelFileNumber(relation->rd_rel->reltablespace, NULL, persistence); } - else if (relation->rd_rel->relkind == RELKIND_INDEX) + else if (relation->rd_rel->relkind == RELKIND_INDEX || + relation->rd_rel->relkind == RELKIND_GLOBAL_INDEX) { if (!OidIsValid(binary_upgrade_next_index_pg_class_relfilenumber)) ereport(ERROR, @@ -6120,7 +6127,8 @@ load_relcache_init_file(bool shared) * If it's an index, there's more to do. Note we explicitly ignore * partitioned indexes here. */ - if (rel->rd_rel->relkind == RELKIND_INDEX) + if (rel->rd_rel->relkind == RELKIND_INDEX || + rel->rd_rel->relkind == RELKIND_GLOBAL_INDEX) { MemoryContext indexcxt; Oid *opfamily; @@ -6515,7 +6523,8 @@ write_relcache_init_file(bool shared) * If it's an index, there's more to do. Note we explicitly ignore * partitioned indexes here. */ - if (rel->rd_rel->relkind == RELKIND_INDEX) + if (rel->rd_rel->relkind == RELKIND_INDEX || + rel->rd_rel->relkind == RELKIND_GLOBAL_INDEX) { /* write the pg_index tuple */ /* we assume this was created by heap_copytuple! */ diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 2eae519b1d..149281167e 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -1874,6 +1874,7 @@ describeOneTableDetails(const char *schemaname, attgenerated_col = cols++; } if (tableinfo.relkind == RELKIND_INDEX || + tableinfo.relkind == RELKIND_GLOBAL_INDEX || tableinfo.relkind == RELKIND_PARTITIONED_INDEX) { if (pset.sversion >= 110000) @@ -1914,6 +1915,7 @@ describeOneTableDetails(const char *schemaname, /* stats target, if relevant to relkind */ if (tableinfo.relkind == RELKIND_RELATION || tableinfo.relkind == RELKIND_INDEX || + tableinfo.relkind == RELKIND_GLOBAL_INDEX || tableinfo.relkind == RELKIND_PARTITIONED_INDEX || tableinfo.relkind == RELKIND_MATVIEW || tableinfo.relkind == RELKIND_FOREIGN_TABLE || @@ -1979,6 +1981,14 @@ describeOneTableDetails(const char *schemaname, printfPQExpBuffer(&title, _("Index \"%s.%s\""), schemaname, relationname); break; + case RELKIND_GLOBAL_INDEX: + if (tableinfo.relpersistence == 'u') + printfPQExpBuffer(&title, _("Unlogged global index \"%s.%s\""), + schemaname, relationname); + else + printfPQExpBuffer(&title, _("Global index \"%s.%s\""), + schemaname, relationname); + break; case RELKIND_PARTITIONED_INDEX: if (tableinfo.relpersistence == 'u') printfPQExpBuffer(&title, _("Unlogged partitioned index \"%s.%s\""), @@ -2244,6 +2254,7 @@ describeOneTableDetails(const char *schemaname, } if (tableinfo.relkind == RELKIND_INDEX || + tableinfo.relkind == RELKIND_GLOBAL_INDEX || tableinfo.relkind == RELKIND_PARTITIONED_INDEX) { /* Footer information about an index */ @@ -2344,7 +2355,7 @@ describeOneTableDetails(const char *schemaname, /* * If it's a partitioned index, we'll print the tablespace below */ - if (tableinfo.relkind == RELKIND_INDEX) + if (tableinfo.relkind == RELKIND_INDEX || tableinfo.relkind == RELKIND_GLOBAL_INDEX) add_tablespace_footer(&cont, tableinfo.relkind, tableinfo.tablespace, true); } @@ -3546,6 +3557,7 @@ add_tablespace_footer(printTableContent *const cont, char relkind, if (relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW || relkind == RELKIND_INDEX || + relkind == RELKIND_GLOBAL_INDEX || relkind == RELKIND_PARTITIONED_TABLE || relkind == RELKIND_PARTITIONED_INDEX || relkind == RELKIND_TOASTVALUE) @@ -3869,6 +3881,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys " WHEN " CppAsString2(RELKIND_VIEW) " THEN '%s'" " WHEN " CppAsString2(RELKIND_MATVIEW) " THEN '%s'" " WHEN " CppAsString2(RELKIND_INDEX) " THEN '%s'" + " WHEN " CppAsString2(RELKIND_GLOBAL_INDEX) " THEN '%s'" " WHEN " CppAsString2(RELKIND_SEQUENCE) " THEN '%s'" " WHEN " CppAsString2(RELKIND_TOASTVALUE) " THEN '%s'" " WHEN " CppAsString2(RELKIND_FOREIGN_TABLE) " THEN '%s'" @@ -3882,6 +3895,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys gettext_noop("view"), gettext_noop("materialized view"), gettext_noop("index"), + gettext_noop("global index"), gettext_noop("sequence"), gettext_noop("TOAST table"), gettext_noop("foreign table"), @@ -3963,6 +3977,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys appendPQExpBufferStr(&buf, CppAsString2(RELKIND_MATVIEW) ","); if (showIndexes) appendPQExpBufferStr(&buf, CppAsString2(RELKIND_INDEX) "," + CppAsString2(RELKIND_GLOBAL_INDEX) "," CppAsString2(RELKIND_PARTITIONED_INDEX) ","); if (showSeq) appendPQExpBufferStr(&buf, CppAsString2(RELKIND_SEQUENCE) ","); diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h index 91c28868d4..1ddfc9af21 100644 --- a/src/include/catalog/index.h +++ b/src/include/catalog/index.h @@ -65,6 +65,7 @@ extern void index_check_primary_key(Relation heapRel, #define INDEX_CREATE_IF_NOT_EXISTS (1 << 4) #define INDEX_CREATE_PARTITIONED (1 << 5) #define INDEX_CREATE_INVALID (1 << 6) +#define INDEX_CREATE_GLOBAL (1 << 7) extern Oid index_create(Relation heapRelation, const char *indexRelationName, diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index e1f4eefa22..eb190e8f14 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -168,6 +168,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd #define RELKIND_FOREIGN_TABLE 'f' /* foreign table */ #define RELKIND_PARTITIONED_TABLE 'p' /* partitioned table */ #define RELKIND_PARTITIONED_INDEX 'I' /* partitioned index */ +#define RELKIND_GLOBAL_INDEX 'g' /* global index */ #define RELPERSISTENCE_PERMANENT 'p' /* regular table */ #define RELPERSISTENCE_UNLOGGED 'u' /* unlogged permanent table */ @@ -194,6 +195,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd #define RELKIND_HAS_STORAGE(relkind) \ ((relkind) == RELKIND_RELATION || \ (relkind) == RELKIND_INDEX || \ + (relkind) == RELKIND_GLOBAL_INDEX || \ (relkind) == RELKIND_SEQUENCE || \ (relkind) == RELKIND_TOASTVALUE || \ (relkind) == RELKIND_MATVIEW) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 7caff62af7..880dd6011a 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2991,6 +2991,7 @@ typedef struct IndexStmt bool if_not_exists; /* just do nothing if index already exists? */ bool reset_default_tblspc; /* reset default_tablespace prior to * executing */ + bool global_index; /* true if index is global */ } IndexStmt; /* ---------------------- diff --git a/src/test/regress/expected/indexing.out b/src/test/regress/expected/indexing.out index 1bdd430f06..eed20063c1 100644 --- a/src/test/regress/expected/indexing.out +++ b/src/test/regress/expected/indexing.out @@ -1421,3 +1421,43 @@ Indexes: "parted_index_col_drop11_b_idx" btree (b) drop table parted_index_col_drop; +-- create global index using non-partition key +create table gidxpart (a int, b int, c text) partition by range (a); +create table gidxpart1 partition of gidxpart for values from (0) to (10); +create table gidxpart2 partition of gidxpart for values from (10) to (100); +create unique index gidx_u on gidxpart using btree(b) global; +select relname, relhasindex, relkind from pg_class where relname like '%gidx%' order by oid; + relname | relhasindex | relkind +-----------------+-------------+--------- + gidxpart | t | p + gidxpart1 | t | r + gidxpart2 | t | r + gidx_u | f | I + gidxpart1_b_idx | f | g + gidxpart2_b_idx | f | g +(6 rows) + +\d+ gidxpart + Partitioned table "public.gidxpart" + Column | Type | Collation | Nullable | Default | Storage | Stats target | Description +--------+---------+-----------+----------+---------+----------+--------------+------------- + a | integer | | | | plain | | + b | integer | | | | plain | | + c | text | | | | extended | | +Partition key: RANGE (a) +Indexes: + "gidx_u" UNIQUE, btree (b) +Partitions: gidxpart1 FOR VALUES FROM (0) TO (10), + gidxpart2 FOR VALUES FROM (10) TO (100) + +\d+ gidx_u + Partitioned index "public.gidx_u" + Column | Type | Key? | Definition | Storage | Stats target +--------+---------+------+------------+---------+-------------- + b | integer | yes | b | plain | +unique, btree, for table "public.gidxpart" +Partitions: gidxpart1_b_idx, + gidxpart2_b_idx + +drop index gidx_u; +drop table gidxpart; diff --git a/src/test/regress/sql/indexing.sql b/src/test/regress/sql/indexing.sql index 429120e710..2169f28e69 100644 --- a/src/test/regress/sql/indexing.sql +++ b/src/test/regress/sql/indexing.sql @@ -760,3 +760,14 @@ alter table parted_index_col_drop drop column c; \d parted_index_col_drop2 \d parted_index_col_drop11 drop table parted_index_col_drop; + +-- create global index using non-partition key +create table gidxpart (a int, b int, c text) partition by range (a); +create table gidxpart1 partition of gidxpart for values from (0) to (10); +create table gidxpart2 partition of gidxpart for values from (10) to (100); +create unique index gidx_u on gidxpart using btree(b) global; +select relname, relhasindex, relkind from pg_class where relname like '%gidx%' order by oid; +\d+ gidxpart +\d+ gidx_u +drop index gidx_u; +drop table gidxpart; -- 2.17.1