diff --git a/doc/src/sgml/ref/alter_index.sgml b/doc/src/sgml/ref/alter_index.sgml index 214005a86c..7157466a7c 100644 --- a/doc/src/sgml/ref/alter_index.sgml +++ b/doc/src/sgml/ref/alter_index.sgml @@ -25,6 +25,8 @@ ALTER INDEX [ IF EXISTS ] name RENA ALTER INDEX [ IF EXISTS ] name SET TABLESPACE tablespace_name ALTER INDEX name ATTACH PARTITION index_name ALTER INDEX name DEPENDS ON EXTENSION extension_name +ALTER INDEX name SET [ NOT ] UNIQUE [ NOT VALID ] +ALTER INDEX name VALIDATE UNIQUE ALTER INDEX name ALTER COLLATION collation_name REFRESH VERSION ALTER INDEX [ IF EXISTS ] name SET ( storage_parameter [= value] [, ... ] ) ALTER INDEX [ IF EXISTS ] name RESET ( storage_parameter [, ... ] ) @@ -113,6 +115,45 @@ ALTER INDEX ALL IN TABLESPACE name + + SET UNIQUE + SET NOT UNIQUE + + + This form alters the uniqueness of the index. The + UNIQUE NOT VALID option implements uniqueness checking + for the specified index immediately, but doesn't validate the uniqueness + of the index. This may mean that some optimizations are not yet + available because the planner will be unable to rely upon the + uniqueness of the index. If the NOT VALID option + is not specified then the lock is held while a potentially lengthy + scan of the index occurs to validate uniqueness. + + + SET NOT UNIQUE requires a + ACCESS EXCLUSIVE lock. + SET UNIQUE requires a + SHARE ROW EXCLUSIVE lock. + + + + + + VALIDATE UNIQUE + + + This form validates the uniqueness of an index previously altered as + UNIQUE NOT VALID by scanning the index. + After the index's uniqueness has been validated this can be relied + upon during optimization in the planner. + + + VALIDATE UNIQUE requires a + SHARE UPDATE EXCLUSIVE lock. + + + + ALTER COLLATION collation_name REFRESH VERSION diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y index 5fcd004e1b..3bd2703b9e 100644 --- a/src/backend/bootstrap/bootparse.y +++ b/src/backend/bootstrap/bootparse.y @@ -342,6 +342,7 @@ Boot_DeclareUniqueIndexStmt: stmt->oldCreateSubid = InvalidSubTransactionId; stmt->oldFirstRelfilenodeSubid = InvalidSubTransactionId; stmt->unique = true; + stmt->uniquevalid = true; stmt->primary = false; stmt->isconstraint = false; stmt->deferrable = false; diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index cffbc0ac38..f77bf66f98 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -609,6 +609,7 @@ UpdateIndexRelation(Oid indexoid, 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_induniqvalid - 1] = BoolGetDatum(indexInfo->ii_UniqueValid); values[Anum_pg_index_indkey - 1] = PointerGetDatum(indkey); values[Anum_pg_index_indcollation - 1] = PointerGetDatum(indcollation); values[Anum_pg_index_indclass - 1] = PointerGetDatum(indclass); @@ -1493,6 +1494,7 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId, const char indexExprs, indexPreds, oldInfo->ii_Unique, + oldInfo->ii_UniqueValid, false, /* not ready for inserts */ true); @@ -2491,6 +2493,7 @@ BuildIndexInfo(Relation index) RelationGetIndexExpressions(index), RelationGetIndexPredicate(index), indexStruct->indisunique, + indexStruct->induniqvalid, indexStruct->indisready, false); @@ -2550,6 +2553,7 @@ BuildDummyIndexInfo(Relation index) RelationGetDummyIndexExpressions(index), NIL, indexStruct->indisunique, + indexStruct->induniqvalid, indexStruct->indisready, false); diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 992f4813b4..6bbf96975b 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -217,7 +217,7 @@ CheckIndexCompatible(Oid oldId, * ii_NumIndexKeyAttrs with same value. */ indexInfo = makeIndexInfo(numberOfAttributes, numberOfAttributes, - accessMethodId, NIL, NIL, false, false, false); + accessMethodId, NIL, NIL, false, false, false, false); typeObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid)); collationObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid)); classObjectId = (Oid *) palloc(numberOfAttributes * sizeof(Oid)); @@ -868,6 +868,7 @@ DefineIndex(Oid relationId, NIL, /* expressions, NIL for now */ make_ands_implicit((Expr *) stmt->whereClause), stmt->unique, + stmt->uniquevalid, !concurrent, concurrent); diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index c5c25ce11d..787bcf646d 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -875,6 +875,7 @@ is_usable_unique_index(Relation indexRel) * operators. */ if (indexStruct->indisunique && + indexStruct->induniqvalid && indexStruct->indimmediate && indexRel->rd_rel->relam == BTREE_AM_OID && indexStruct->indisvalid && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 993da56d43..61f5bfcb70 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -559,6 +559,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx, static List *GetParentedForeignKeyRefs(Relation partition); static void ATDetachCheckNoForeignKeyRefs(Relation partition); static void ATExecAlterCollationRefreshVersion(Relation rel, List *coll); +static void ATExecAlterIndex(Relation rel, bool unique, bool valid); /* ---------------------------------------------------------------- @@ -3790,6 +3791,7 @@ AlterTableGetLockLevel(List *cmds) */ case AT_DropConstraint: /* as DROP INDEX */ case AT_DropNotNull: /* may change some SQL plans */ + case AT_SetIndexNotUnique: cmd_lockmode = AccessExclusiveLock; break; @@ -3832,6 +3834,8 @@ AlterTableGetLockLevel(List *cmds) case AT_DisableTrig: case AT_DisableTrigAll: case AT_DisableTrigUser: + case AT_SetIndexUnique: + case AT_SetIndexUniqueNotValid: cmd_lockmode = ShareRowExclusiveLock; break; @@ -3953,6 +3957,7 @@ AlterTableGetLockLevel(List *cmds) break; case AT_ValidateConstraint: /* Uses MVCC in getConstraints() */ + case AT_ValidateIndexUnique: cmd_lockmode = ShareUpdateExclusiveLock; break; @@ -4365,6 +4370,26 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, /* No command-specific prep needed */ pass = AT_PASS_MISC; break; + case AT_SetIndexNotUnique: + ATSimplePermissions(rel, ATT_INDEX); + /* No command-specific prep needed */ + pass = AT_PASS_MISC; + break; + case AT_SetIndexUnique: + ATSimplePermissions(rel, ATT_INDEX); + /* No command-specific prep needed */ + pass = AT_PASS_MISC; + break; + case AT_SetIndexUniqueNotValid: + ATSimplePermissions(rel, ATT_INDEX); + /* No command-specific prep needed */ + pass = AT_PASS_MISC; + break; + case AT_ValidateIndexUnique: + ATSimplePermissions(rel, ATT_INDEX); + /* No command-specific prep needed */ + pass = AT_PASS_MISC; + break; default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype); @@ -4753,6 +4778,17 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, Assert(rel->rd_rel->relkind == RELKIND_INDEX); ATExecAlterCollationRefreshVersion(rel, cmd->object); break; + case AT_SetIndexNotUnique: + ATExecAlterIndex(rel, false, true); + break; + case AT_SetIndexUnique: + ATExecAlterIndex(rel, true, true); + break; + case AT_SetIndexUniqueNotValid: + ATExecAlterIndex(rel, true, false); + break; + case AT_ValidateIndexUnique: + break; default: /* oops */ elog(ERROR, "unrecognized alter table type: %d", (int) cmd->subtype); @@ -10295,7 +10331,7 @@ transformFkeyCheckAttrs(Relation pkrel, /* * Must have the right number of columns; must be unique and not a * partial index; forget it if there are any expressions, too. Invalid - * indexes are out as well. + * indexes are out as well. Doesn't matter is uniqueness is valid yet. */ if (indexStruct->indnkeyatts == numattrs && indexStruct->indisunique && @@ -14713,7 +14749,8 @@ ATExecReplicaIdentity(Relation rel, ReplicaIdentityStmt *stmt, LOCKMODE lockmode RelationGetRelationName(rel)))); /* The AM must support uniqueness, and the index must in fact be unique. */ if (!indexRel->rd_indam->amcanunique || - !indexRel->rd_index->indisunique) + !indexRel->rd_index->indisunique || + !indexRel->rd_index->induniqvalid) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot use non-unique index \"%s\" as replica identity", @@ -17631,3 +17668,39 @@ ATExecAlterCollationRefreshVersion(Relation rel, List *coll) index_update_collation_versions(rel->rd_id, get_collation_oid(coll, false)); CacheInvalidateRelcache(rel); } + +static void +ATExecAlterIndex(Relation rel, bool unique, bool valid) +{ + Relation pg_index; + HeapTuple pg_index_tuple; + Form_pg_index pg_index_form; + bool dirty = false; + + pg_index = table_open(IndexRelationId, RowExclusiveLock); + pg_index_tuple = SearchSysCacheCopy1(INDEXRELID, + ObjectIdGetDatum(RelationGetRelid(rel))); + if (!HeapTupleIsValid(pg_index_tuple)) + elog(ERROR, "cache lookup failed for index %u", RelationGetRelid(rel)); + pg_index_form = (Form_pg_index) GETSTRUCT(pg_index_tuple); + + if (pg_index_form->indisunique != unique) + { + pg_index_form->indisunique = unique; + dirty = true; + + if (unique && valid) + Assert(true); // Call validation + pg_index_form->induniqvalid = valid; + } + + if (dirty) + { + CatalogTupleUpdate(pg_index, &pg_index_tuple->t_self, pg_index_tuple); + InvokeObjectPostAlterHookArg(IndexRelationId, RelationGetRelid(rel), 0, + InvalidOid, true); + } + + heap_freetuple(pg_index_tuple); + table_close(pg_index, RowExclusiveLock); +} diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c index 2aafcc8f22..3f6429282a 100644 --- a/src/backend/executor/execIndexing.c +++ b/src/backend/executor/execIndexing.c @@ -379,6 +379,8 @@ ExecInsertIndexTuples(ResultRelInfo *resultRelInfo, * * For a speculative insertion (used by INSERT ... ON CONFLICT), do * the same as for a deferrable unique index. + * + * It doesn't matter here whether uniqueness is valid or not. */ if (!indexRelation->rd_index->indisunique) checkUnique = UNIQUE_CHECK_NO; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index ba3ccc712c..40015f4f30 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3531,6 +3531,7 @@ _copyIndexStmt(const IndexStmt *from) COPY_SCALAR_FIELD(oldCreateSubid); COPY_SCALAR_FIELD(oldFirstRelfilenodeSubid); COPY_SCALAR_FIELD(unique); + COPY_SCALAR_FIELD(uniquevalid); COPY_SCALAR_FIELD(primary); COPY_SCALAR_FIELD(isconstraint); COPY_SCALAR_FIELD(deferrable); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index a2ef853dc2..c0d28287d6 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1358,6 +1358,7 @@ _equalIndexStmt(const IndexStmt *a, const IndexStmt *b) COMPARE_SCALAR_FIELD(oldCreateSubid); COMPARE_SCALAR_FIELD(oldFirstRelfilenodeSubid); COMPARE_SCALAR_FIELD(unique); + COMPARE_SCALAR_FIELD(uniquevalid); COMPARE_SCALAR_FIELD(primary); COMPARE_SCALAR_FIELD(isconstraint); COMPARE_SCALAR_FIELD(deferrable); diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index 01c110cd2f..ef8083be74 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -741,7 +741,7 @@ make_ands_implicit(Expr *clause) */ IndexInfo * makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions, - List *predicates, bool unique, bool isready, bool concurrent) + List *predicates, bool unique, bool uniquevalid, bool isready, bool concurrent) { IndexInfo *n = makeNode(IndexInfo); @@ -750,6 +750,7 @@ makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions, Assert(n->ii_NumIndexKeyAttrs != 0); Assert(n->ii_NumIndexKeyAttrs <= n->ii_NumIndexAttrs); n->ii_Unique = unique; + n->ii_UniqueValid = uniquevalid; n->ii_ReadyForInserts = isready; n->ii_Concurrent = concurrent; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 8392be6d44..950e1bdade 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2697,6 +2697,7 @@ _outIndexStmt(StringInfo str, const IndexStmt *node) WRITE_UINT_FIELD(oldCreateSubid); WRITE_UINT_FIELD(oldFirstRelfilenodeSubid); WRITE_BOOL_FIELD(unique); + WRITE_BOOL_FIELD(uniquevalid); WRITE_BOOL_FIELD(primary); WRITE_BOOL_FIELD(isconstraint); WRITE_BOOL_FIELD(deferrable); diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index da322b453e..cf2ff2d866 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -396,6 +396,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, info->indrestrictinfo = NIL; /* set later, in indxpath.c */ info->predOK = false; /* set later, in indxpath.c */ info->unique = index->indisunique; + info->uniquevalid = index->induniqvalid; info->immediate = index->indimmediate; info->hypothetical = false; @@ -726,7 +727,8 @@ infer_arbiter_indexes(PlannerInfo *root) * Note that we do not perform a check against indcheckxmin (like e.g. * get_relation_info()) here to eliminate candidates, because * uniqueness checking only cares about the most recently committed - * tuple versions. + * tuple versions. So we allow an index that is defined as unique + * even if the uniqueness is not yet valid. */ /* diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 31c95443a5..2c8f44c09c 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -300,7 +300,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type add_drop opt_asc_desc opt_nulls_order %type alter_table_cmd alter_type_cmd opt_collate_clause - replica_identity partition_cmd index_partition_cmd + replica_identity partition_cmd index_partition_cmd alter_index_cmd %type alter_table_cmds alter_type_cmds %type alter_identity_column_option_list %type alter_identity_column_option @@ -1961,6 +1961,15 @@ AlterTableStmt: n->missing_ok = false; $$ = (Node *)n; } + | ALTER INDEX qualified_name alter_index_cmd + { + AlterTableStmt *n = makeNode(AlterTableStmt); + n->relation = $3; + n->cmds = list_make1($4); + n->objtype = OBJECT_INDEX; + n->missing_ok = false; + $$ = (Node *)n; + } | ALTER INDEX ALL IN_P TABLESPACE name SET TABLESPACE name opt_nowait { AlterTableMoveAllStmt *n = @@ -2129,6 +2138,38 @@ index_partition_cmd: } ; +alter_index_cmd: + /* ALTER INDEX SET [NOT] UNIQUE [NOT VALID] */ + SET NOT UNIQUE + { + AlterTableCmd *n = makeNode(AlterTableCmd); + + n->subtype = AT_SetIndexNotUnique; + $$ = (Node *)n; + } + | SET UNIQUE + { + AlterTableCmd *n = makeNode(AlterTableCmd); + + n->subtype = AT_SetIndexUnique; + $$ = (Node *)n; + } + | SET UNIQUE NOT VALID + { + AlterTableCmd *n = makeNode(AlterTableCmd); + + n->subtype = AT_SetIndexUniqueNotValid; + $$ = (Node *)n; + } + | VALIDATE UNIQUE + { + AlterTableCmd *n = makeNode(AlterTableCmd); + + n->subtype = AT_ValidateIndexUnique; + $$ = (Node *)n; + } + ; + alter_table_cmd: /* ALTER TABLE ADD */ ADD_P columnDef @@ -7224,6 +7265,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name { IndexStmt *n = makeNode(IndexStmt); n->unique = $2; + n->uniquevalid = $2; n->concurrent = $4; n->idxname = $5; n->relation = $7; @@ -7254,6 +7296,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_index_name { IndexStmt *n = makeNode(IndexStmt); n->unique = $2; + n->uniquevalid = $2; n->concurrent = $4; n->idxname = $8; n->relation = $10; diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index b31f3afa03..e85c1e79a5 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1570,6 +1570,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx, index->oldCreateSubid = InvalidSubTransactionId; index->oldFirstRelfilenodeSubid = InvalidSubTransactionId; index->unique = idxrec->indisunique; + index->uniquevalid = idxrec->induniqvalid; index->primary = idxrec->indisprimary; index->transformed = true; /* don't need transformIndexStmt */ index->concurrent = false; @@ -2114,6 +2115,7 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt) index = makeNode(IndexStmt); index->unique = (constraint->contype != CONSTR_EXCLUSION); + index->uniquevalid = true; index->primary = (constraint->contype == CONSTR_PRIMARY); if (index->primary) { diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c index ba95755867..00454c7ed9 100644 --- a/src/backend/utils/cache/catcache.c +++ b/src/backend/utils/cache/catcache.c @@ -1056,6 +1056,7 @@ InitCatCachePhase2(CatCache *cache, bool touch_index) * about the pg_am indexes not getting tested. */ Assert(idesc->rd_index->indisunique && + idesc->rd_index->induniqvalid && idesc->rd_index->indimmediate); index_close(idesc, AccessShareLock); diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 7ef510cd01..64a9d23773 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -2261,6 +2261,7 @@ RelationReloadIndexInfo(Relation relation) relation->rd_index->indcheckxmin = index->indcheckxmin; relation->rd_index->indisready = index->indisready; relation->rd_index->indislive = index->indislive; + relation->rd_index->induniqvalid = index->induniqvalid; /* Copy xmin too, as that is needed to make sense of indcheckxmin */ HeapTupleHeaderSetXmin(relation->rd_indextuple->t_data, @@ -4580,7 +4581,7 @@ RelationGetIndexList(Relation relation) * so don't check them. */ if (!index->indisvalid || !index->indisunique || - !index->indimmediate || + !index->indimmediate || !index->induniqvalid || !heap_attisnull(htup, Anum_pg_index_indpred, NULL)) continue; @@ -5060,7 +5061,10 @@ restart: else indexPredicate = NULL; - /* Can this index be referenced by a foreign key? */ + /* + * Can this index be referenced by a foreign key? + * Yes, even if uniqueness is not yet valid (!induniqvalid) + */ isKey = indexDesc->rd_index->indisunique && indexExpressions == NULL && indexPredicate == NULL; diff --git a/src/include/catalog/pg_index.h b/src/include/catalog/pg_index.h index b6d7ebec55..551499c93f 100644 --- a/src/include/catalog/pg_index.h +++ b/src/include/catalog/pg_index.h @@ -42,6 +42,7 @@ CATALOG(pg_index,2610,IndexRelationId) BKI_SCHEMA_MACRO bool indisready; /* is this index ready for inserts? */ bool indislive; /* is this index alive at all? */ bool indisreplident; /* is this index the identity for replication? */ + bool induniqvalid; /* is uniqueness valid for use by queries? */ /* variable-length fields start here, but we allow direct access to indkey */ int2vector indkey BKI_FORCE_NOT_NULL; /* column numbers of indexed cols, diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 48c3f570fa..18fd3e7e9e 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -169,6 +169,7 @@ typedef struct IndexInfo uint16 *ii_UniqueStrats; /* array with one entry per column */ Datum *ii_OpclassOptions; /* array with one entry per column */ bool ii_Unique; + bool ii_UniqueValid; bool ii_ReadyForInserts; bool ii_Concurrent; bool ii_BrokenHotChain; diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h index 48a7ebfe45..827b4d795f 100644 --- a/src/include/nodes/makefuncs.h +++ b/src/include/nodes/makefuncs.h @@ -96,7 +96,7 @@ extern List *make_ands_implicit(Expr *clause); extern IndexInfo *makeIndexInfo(int numattrs, int numkeyattrs, Oid amoid, List *expressions, List *predicates, - bool unique, bool isready, bool concurrent); + bool unique, bool uniquevalid, bool isready, bool concurrent); extern DefElem *makeDefElem(char *name, Node *arg, int location); extern DefElem *makeDefElemExtended(char *nameSpace, char *name, Node *arg, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index dc2bb40926..3107694899 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1873,7 +1873,11 @@ typedef enum AlterTableType AT_AddIdentity, /* ADD IDENTITY */ AT_SetIdentity, /* SET identity column options */ AT_DropIdentity, /* DROP IDENTITY */ - AT_AlterCollationRefreshVersion /* ALTER COLLATION ... REFRESH VERSION */ + AT_AlterCollationRefreshVersion, /* ALTER COLLATION ... REFRESH VERSION */ + AT_SetIndexNotUnique, /* ALTER INDEX SET NOT UNIQUE */ + AT_SetIndexUnique, /* ALTER INDEX SET UNIQUE */ + AT_SetIndexUniqueNotValid, /* ALTER INDEX SET UNIQUE NOT VALID */ + AT_ValidateIndexUnique /* ALTER INDEX VALIDATE UNIQUE */ } AlterTableType; typedef struct ReplicaIdentityStmt @@ -2806,6 +2810,7 @@ typedef struct IndexStmt SubTransactionId oldFirstRelfilenodeSubid; /* rd_firstRelfilenodeSubid of * oldNode */ bool unique; /* is index unique? */ + bool uniquevalid; /* is index uniqueness valid? */ bool primary; /* is index a primary key? */ bool isconstraint; /* is it for a pkey/unique constraint? */ bool deferrable; /* is the constraint DEFERRABLE? */ diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index cde2637798..eff6714fcd 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -853,6 +853,7 @@ struct IndexOptInfo bool predOK; /* true if index predicate matches query */ bool unique; /* true if a unique index */ + bool uniquevalid; /* true if uniqueness has been validated */ bool immediate; /* is uniqueness enforced immediately? */ bool hypothetical; /* true if index doesn't really exist */ diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index 0ce6ee4622..ed7b2f1408 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -228,6 +228,11 @@ ALTER INDEX IF EXISTS __attmp_onek_unique1 RENAME TO onek_unique1; NOTICE: relation "__attmp_onek_unique1" does not exist, skipping ALTER INDEX onek_unique1 RENAME TO attmp_onek_unique1; ALTER INDEX attmp_onek_unique1 RENAME TO onek_unique1; +ALTER INDEX onek_unique1 SET NOT UNIQUE; +ALTER INDEX onek_unique1 SET UNIQUE NOT VALID; +ALTER INDEX onek_unique1 VALIDATE UNIQUE; +ALTER INDEX onek_unique1 SET NOT UNIQUE; +ALTER INDEX onek_unique1 SET UNIQUE; SET ROLE regress_alter_table_user1; ALTER INDEX onek_unique1 RENAME TO fail; -- permission denied ERROR: must be owner of index onek_unique1 diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index 4cc55d8525..f6ce1d695b 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -223,6 +223,12 @@ ALTER INDEX IF EXISTS __attmp_onek_unique1 RENAME TO onek_unique1; ALTER INDEX onek_unique1 RENAME TO attmp_onek_unique1; ALTER INDEX attmp_onek_unique1 RENAME TO onek_unique1; +ALTER INDEX onek_unique1 SET NOT UNIQUE; +ALTER INDEX onek_unique1 SET UNIQUE NOT VALID; +ALTER INDEX onek_unique1 VALIDATE UNIQUE; +ALTER INDEX onek_unique1 SET NOT UNIQUE; +ALTER INDEX onek_unique1 SET UNIQUE; + SET ROLE regress_alter_table_user1; ALTER INDEX onek_unique1 RENAME TO fail; -- permission denied RESET ROLE;