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;