v2-0001-Introduce-the-ability-to-enable-disable-indexes.patch
application/octet-stream
Filename: v2-0001-Introduce-the-ability-to-enable-disable-indexes.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v2-0001
Subject: Introduce the ability to enable/disable indexes
| File | + | − |
|---|---|---|
| doc/src/sgml/catalogs.sgml | 11 | 0 |
| doc/src/sgml/ref/alter_index.sgml | 39 | 0 |
| doc/src/sgml/ref/create_index.sgml | 29 | 0 |
| src/backend/bootstrap/bootparse.y | 2 | 0 |
| src/backend/catalog/index.c | 27 | 4 |
| src/backend/catalog/toasting.c | 1 | 1 |
| src/backend/commands/indexcmds.c | 4 | 0 |
| src/backend/commands/tablecmds.c | 93 | 1 |
| src/backend/optimizer/path/indxpath.c | 12 | 0 |
| src/backend/optimizer/util/plancat.c | 2 | 0 |
| src/backend/parser/gram.y | 45 | 3 |
| src/backend/parser/parse_utilcmd.c | 3 | 0 |
| src/backend/utils/adt/ruleutils.c | 4 | 0 |
| src/backend/utils/cache/relcache.c | 1 | 0 |
| src/include/catalog/catversion.h | 1 | 1 |
| src/include/catalog/index.h | 1 | 1 |
| src/include/catalog/pg_index.h | 1 | 0 |
| src/include/nodes/parsenodes.h | 3 | 0 |
| src/include/nodes/pathnodes.h | 2 | 0 |
| src/test/regress/expected/create_index.out | 250 | 0 |
| src/test/regress/sql/create_index.sql | 140 | 0 |
From 848b143d6ae1817602aa68710e9bcc033c061da9 Mon Sep 17 00:00:00 2001
From: Shayon Mukherjee <shayonj@gmail.com>
Date: Sun, 22 Sep 2024 14:01:45 -0400
Subject: [PATCH v2] Introduce the ability to enable/disable indexes
---
doc/src/sgml/catalogs.sgml | 11 +
doc/src/sgml/ref/alter_index.sgml | 39 ++++
doc/src/sgml/ref/create_index.sgml | 29 +++
src/backend/bootstrap/bootparse.y | 2 +
src/backend/catalog/index.c | 31 ++-
src/backend/catalog/toasting.c | 2 +-
src/backend/commands/indexcmds.c | 4 +
src/backend/commands/tablecmds.c | 94 +++++++-
src/backend/optimizer/path/indxpath.c | 12 +
src/backend/optimizer/util/plancat.c | 2 +
src/backend/parser/gram.y | 48 +++-
src/backend/parser/parse_utilcmd.c | 3 +
src/backend/utils/adt/ruleutils.c | 4 +
src/backend/utils/cache/relcache.c | 1 +
src/include/catalog/catversion.h | 2 +-
src/include/catalog/index.h | 2 +-
src/include/catalog/pg_index.h | 1 +
src/include/nodes/parsenodes.h | 3 +
src/include/nodes/pathnodes.h | 2 +
src/test/regress/expected/create_index.out | 250 +++++++++++++++++++++
src/test/regress/sql/create_index.sql | 140 ++++++++++++
21 files changed, 671 insertions(+), 11 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index bfb97865e1..61fbf5beb5 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -4590,6 +4590,17 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
partial index.
</para></entry>
</row>
+
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>indisenabled</structfield> <type>bool</type>
+ </para>
+ <para>
+ If true, the index is currently enabled and should be used for queries.
+ If false, the index is disabled and should not be used for queries,
+ but is still maintained when the table is modified. Default is true.
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/alter_index.sgml b/doc/src/sgml/ref/alter_index.sgml
index e26efec064..613d63b747 100644
--- a/doc/src/sgml/ref/alter_index.sgml
+++ b/doc/src/sgml/ref/alter_index.sgml
@@ -31,6 +31,8 @@ ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> ALTE
SET STATISTICS <replaceable class="parameter">integer</replaceable>
ALTER INDEX ALL IN TABLESPACE <replaceable class="parameter">name</replaceable> [ OWNED BY <replaceable class="parameter">role_name</replaceable> [, ... ] ]
SET TABLESPACE <replaceable class="parameter">new_tablespace</replaceable> [ NOWAIT ]
+ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> ENABLE
+ALTER INDEX [ IF EXISTS ] <replaceable class="parameter">name</replaceable> DISABLE
</synopsis>
</refsynopsisdiv>
@@ -158,6 +160,29 @@ ALTER INDEX ALL IN TABLESPACE <replaceable class="parameter">name</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>ENABLE</literal></term>
+ <listitem>
+ <para>
+ Enable the specified index. The index will be used by the query planner
+ for query optimization. This is the default state for newly created indexes.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>DISABLE</literal></term>
+ <listitem>
+ <para>
+ Disable the specified index. A disabled index is not used by the query planner
+ for query optimization, but it is still maintained when the underlying table
+ data changes. This can be useful for testing query performance with and without
+ specific indexes, or for temporarily reducing the overhead of index maintenance
+ during bulk data loading operations.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
@@ -300,6 +325,20 @@ CREATE INDEX coord_idx ON measured (x, y, (z + t));
ALTER INDEX coord_idx ALTER COLUMN 3 SET STATISTICS 1000;
</programlisting></para>
+ <para>
+ To enable an index:
+<programlisting>
+ALTER INDEX idx_name ENABLE;
+</programlisting>
+ </para>
+
+ <para>
+ To disable an index:
+<programlisting>
+ALTER INDEX idx_name DISABLE;
+</programlisting>
+ </para>
+
</refsect1>
<refsect1>
diff --git a/doc/src/sgml/ref/create_index.sgml b/doc/src/sgml/ref/create_index.sgml
index 621bc0e253..f1eebfa250 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 ] <replaceable class=
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
[ WHERE <replaceable class="parameter">predicate</replaceable> ]
+ [ DISABLE ]
</synopsis>
</refsynopsisdiv>
@@ -380,6 +381,19 @@ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] <replaceable class=
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>DISABLE</literal></term>
+ <listitem>
+ <para>
+ Creates the index in a disabled state (default enabled). A disabled index is not used by the
+ query planner for query optimization, but it is still maintained when the
+ underlying table data changes. This can be useful when you want to create
+ an index without immediately impacting query performance, allowing you to
+ enable it later at a more convenient time. The index can be enabled later
+ using <command>ALTER INDEX ... ENABLE</command>.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
<refsect2 id="sql-createindex-storage-parameters" xreflabel="Index Storage Parameters">
@@ -701,6 +715,14 @@ Indexes:
partitioned index is a metadata only operation.
</para>
+ <para>
+ When creating an index with the <literal>DISABLE</literal> option, the index
+ will be created but not used for query planning. This can be useful for
+ preparing an index in advance of its use, or for testing purposes. The index
+ will still be maintained as the table is modified, so it can be enabled
+ later without needing to be rebuilt. By default all new indexes are enabled.
+ </para>
+
</refsect2>
</refsect1>
@@ -980,6 +1002,13 @@ SELECT * FROM points
CREATE INDEX CONCURRENTLY sales_quantity_index ON sales_table (quantity);
</programlisting></para>
+ <para>
+ To create an index on the table <literal>test_table</literal> with the default
+ name, but have it initially disabled:
+<programlisting>
+CREATE INDEX ON test_table (col1) DISABLE;
+</programlisting>
+ </para>
</refsect1>
<refsect1>
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 73a7592fb7..6023d58490 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -302,6 +302,7 @@ Boot_DeclareIndexStmt:
stmt->concurrent = false;
stmt->if_not_exists = false;
stmt->reset_default_tblspc = false;
+ stmt->isenabled = true;
/* locks and races need not concern us in bootstrap mode */
relationId = RangeVarGetRelid(stmt->relation, NoLock,
@@ -355,6 +356,7 @@ Boot_DeclareUniqueIndexStmt:
stmt->concurrent = false;
stmt->if_not_exists = false;
stmt->reset_default_tblspc = false;
+ stmt->isenabled = true;
/* locks and races need not concern us in bootstrap mode */
relationId = RangeVarGetRelid(stmt->relation, NoLock,
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index b2b3ecb524..1989cb8ce9 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -118,7 +118,8 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
bool isexclusion,
bool immediate,
bool isvalid,
- bool isready);
+ bool isready,
+ bool isenabled);
static void index_update_stats(Relation rel,
bool hasindex,
double reltuples);
@@ -569,7 +570,8 @@ UpdateIndexRelation(Oid indexoid,
bool isexclusion,
bool immediate,
bool isvalid,
- bool isready)
+ bool isready,
+ bool isenabled)
{
int2vector *indkey;
oidvector *indcollation;
@@ -647,6 +649,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_indisenabled - 1] = BoolGetDatum(isenabled);
values[Anum_pg_index_indkey - 1] = PointerGetDatum(indkey);
values[Anum_pg_index_indcollation - 1] = PointerGetDatum(indcollation);
values[Anum_pg_index_indclass - 1] = PointerGetDatum(indclass);
@@ -712,6 +715,8 @@ UpdateIndexRelation(Oid indexoid,
* already exists.
* INDEX_CREATE_PARTITIONED:
* create a partitioned index (table must be partitioned)
+ * INDEX_CREATE_DISABLED:
+* create the index as disabled if instructed, defaults to being enabled.
* 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
@@ -757,6 +762,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 isenabled = (flags & INDEX_CREATE_ENABLED) != 0;
char relkind;
TransactionId relfrozenxid;
MultiXactId relminmxid;
@@ -1040,13 +1046,15 @@ index_create(Relation heapRelation,
* (Or, could define a rule to maintain the predicate) --Nels, Feb '92
* ----------------
*/
+
UpdateIndexRelation(indexRelationId, heapRelationId, parentIndexRelid,
indexInfo,
collationIds, opclassIds, coloptions,
isprimary, is_exclusion,
(constr_flags & INDEX_CONSTR_CREATE_DEFERRABLE) == 0,
!concurrent && !invalid,
- !concurrent);
+ !concurrent,
+ isenabled);
/*
* Register relcache invalidation on the indexes' heap relation, to
@@ -1315,6 +1323,8 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
List *indexColNames = NIL;
List *indexExprs = NIL;
List *indexPreds = NIL;
+ Form_pg_index indexForm;
+ bits16 createFlags;
indexRelation = index_open(oldIndexId, RowExclusiveLock);
@@ -1342,6 +1352,9 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
Anum_pg_index_indoption);
indcoloptions = (int2vector *) DatumGetPointer(colOptionDatum);
+ /* Get the enabled state of the original index */
+ indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
+
/* Fetch reloptions of index if any */
classTuple = SearchSysCache1(RELOID, ObjectIdGetDatum(oldIndexId));
if (!HeapTupleIsValid(classTuple))
@@ -1433,6 +1446,16 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
stattargets[i].isnull = isnull;
}
+ /*
+ * Determine the create flags for the new index.
+ * We always use SKIP_BUILD and CONCURRENT for concurrent reindexing.
+ * If the original index was enabled, we also set the ENABLED flag
+ * to maintain the same state in the new index.
+ */
+ createFlags = INDEX_CREATE_SKIP_BUILD | INDEX_CREATE_CONCURRENT;
+ if (indexForm->indisenabled)
+ createFlags |= INDEX_CREATE_ENABLED;
+
/*
* Now create the new index.
*
@@ -1456,7 +1479,7 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
indcoloptions->values,
stattargets,
reloptionsDatum,
- INDEX_CREATE_SKIP_BUILD | INDEX_CREATE_CONCURRENT,
+ createFlags,
0,
true, /* allow table to be a system catalog? */
false, /* is_internal? */
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 738bc46ae8..ce497eed58 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -324,7 +324,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
BTREE_AM_OID,
rel->rd_rel->reltablespace,
collationIds, opclassIds, NULL, coloptions, NULL, (Datum) 0,
- INDEX_CREATE_IS_PRIMARY, 0, true, true, NULL);
+ INDEX_CREATE_IS_PRIMARY | INDEX_CREATE_ENABLED, 0, true, true, NULL);
table_close(toast_rel, NoLock);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index f99c2d2dee..7cd2041300 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1191,6 +1191,10 @@ DefineIndex(Oid tableId,
flags |= INDEX_CREATE_PARTITIONED;
if (stmt->primary)
flags |= INDEX_CREATE_IS_PRIMARY;
+ if (stmt->isenabled)
+ flags |= INDEX_CREATE_ENABLED;
+ else
+ flags &= ~INDEX_CREATE_ENABLED;
/*
* If the table is partitioned, and recursion was declined but partitions
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2d703aa22e..905a3f58a2 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -664,7 +664,7 @@ static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
static char GetAttributeCompression(Oid atttypid, const char *compression);
static char GetAttributeStorage(Oid atttypid, const char *storagemode);
-
+static void ATExecEnableDisableIndex(Relation rel, bool enable);
/* ----------------------------------------------------------------
* DefineRelation
@@ -4546,6 +4546,8 @@ AlterTableGetLockLevel(List *cmds)
case AT_SetExpression:
case AT_DropExpression:
case AT_SetCompression:
+ case AT_EnableIndex:
+ case AT_DisableIndex:
cmd_lockmode = AccessExclusiveLock;
break;
@@ -5123,6 +5125,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* No command-specific prep needed */
pass = AT_PASS_MISC;
break;
+ case AT_EnableIndex:
+ case AT_DisableIndex:
+ ATSimplePermissions(cmd->subtype, rel, ATT_INDEX | ATT_PARTITIONED_INDEX);
+ /* No command-specific prep needed */
+ pass = AT_PASS_MISC;
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -5519,6 +5527,12 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
case AT_DetachPartitionFinalize:
address = ATExecDetachPartitionFinalize(rel, ((PartitionCmd *) cmd->def)->name);
break;
+ case AT_EnableIndex:
+ ATExecEnableDisableIndex(rel, true);
+ break;
+ case AT_DisableIndex:
+ ATExecEnableDisableIndex(rel, false);
+ break;
default: /* oops */
elog(ERROR, "unrecognized alter table type: %d",
(int) cmd->subtype);
@@ -6418,6 +6432,8 @@ alter_table_type_to_string(AlterTableType cmdtype)
return "DROP COLUMN";
case AT_AddIndex:
case AT_ReAddIndex:
+ case AT_EnableIndex:
+ case AT_DisableIndex:
return NULL; /* not real grammar */
case AT_AddConstraint:
case AT_ReAddConstraint:
@@ -20214,3 +20230,79 @@ GetAttributeStorage(Oid atttypid, const char *storagemode)
return cstorage;
}
+
+/*
+ * ATExecEnableDisableIndex function handles the execution of enabling or disabling an index.
+ * It performs an in-place update to preserve the pg_index row's xmin.
+ */
+static void
+ATExecEnableDisableIndex(Relation rel, bool enable)
+{
+ Oid indexOid = RelationGetRelid(rel);
+ Relation pg_index;
+ HeapTuple indexTuple;
+ Form_pg_index indexForm;
+ bool updated = false;
+
+ /* Open pg_index */
+ pg_index = table_open(IndexRelationId, RowExclusiveLock);
+
+ /* Fetch the index's pg_index tuple */
+ indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexOid));
+ if (!HeapTupleIsValid(indexTuple))
+ elog(ERROR, "cache lookup failed for index %u", indexOid);
+
+ indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
+
+ /* Check if the index's current state differs from the desired state */
+ if (indexForm->indisenabled != enable)
+ {
+ HeapTuple copyTuple;
+
+ /* Create a modifiable copy of the tuple */
+ copyTuple = heap_copytuple(indexTuple);
+ indexForm = (Form_pg_index) GETSTRUCT(copyTuple);
+
+ indexForm->indisenabled = enable;
+
+ /* Perform an in-place update */
+ heap_inplace_update(pg_index, copyTuple);
+
+ /* Free the copy */
+ heap_freetuple(copyTuple);
+
+ updated = true;
+
+ /* Update relcache */
+ CacheInvalidateRelcache(rel);
+
+ /*
+ * Invalidate the relcache for the table, so that after we commit
+ * all sessions will refresh the table's index state before
+ * attempting to use this index.
+ */
+ CacheInvalidateRelcache(rel);
+
+ ereport(NOTICE,
+ (errmsg("index \"%s\" is now %s",
+ RelationGetRelationName(rel),
+ enable ? "enabled" : "disabled")));
+ }
+ else
+ {
+ ereport(NOTICE,
+ (errmsg("index \"%s\" is already %s",
+ RelationGetRelationName(rel),
+ enable ? "enabled" : "disabled")));
+ }
+
+ /* Clean up */
+ ReleaseSysCache(indexTuple);
+ table_close(pg_index, RowExclusiveLock);
+
+ /* Invoke the object access hook if we updated the index */
+ if (updated)
+ {
+ InvokeObjectPostAlterHook(IndexRelationId, indexOid, 0);
+ }
+}
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index c0fcc7d78d..843237dadf 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -254,6 +254,10 @@ create_index_paths(PlannerInfo *root, RelOptInfo *rel)
{
IndexOptInfo *index = (IndexOptInfo *) lfirst(lc);
+ /* Skip disabled indexes */
+ if (!index->enabled)
+ continue;
+
/* Protect limited-size array in IndexClauseSets */
Assert(index->nkeycolumns <= INDEX_MAX_KEYS);
@@ -715,6 +719,10 @@ get_index_paths(PlannerInfo *root, RelOptInfo *rel,
bool skip_nonnative_saop = false;
ListCell *lc;
+ /* Skip disabled indexes */
+ if (!index->enabled)
+ return;
+
/*
* Build simple index paths using the clauses. Allow ScalarArrayOpExpr
* clauses only if the index AM supports them natively.
@@ -823,6 +831,10 @@ build_index_paths(PlannerInfo *root, RelOptInfo *rel,
Assert(skip_nonnative_saop != NULL || scantype == ST_BITMAPSCAN);
+ /* Skip disabled indexes */
+ if (!index->enabled)
+ return NIL;
+
/*
* Check that index supports the desired scan type(s)
*/
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index b913f91ff0..30dc6353f6 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -192,6 +192,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
}
}
+
/*
* Estimate relation size --- unless it's an inheritance parent, in which
* case the size we want is not the rel's own size but the size of its
@@ -459,6 +460,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
info->unique = index->indisunique;
info->immediate = index->indimmediate;
info->hypothetical = false;
+ info->enabled = index->indisenabled;
/*
* Estimate the index size. If it's not a partial index, we lock
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ab304ca989..7e47541541 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -333,7 +333,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 index_partition_cmd
+ replica_identity partition_cmd index_partition_cmd index_alter_cmd
%type <list> alter_table_cmds alter_type_cmds
%type <list> alter_identity_column_option_list
%type <defelt> alter_identity_column_option
@@ -496,6 +496,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <boolean> opt_unique opt_verbose opt_full
%type <boolean> opt_freeze opt_analyze opt_default
%type <defelt> opt_binary copy_delimiter
+%type <boolean> opt_index_enabled
%type <boolean> copy_from opt_program
@@ -2144,6 +2145,24 @@ AlterTableStmt:
n->nowait = $13;
$$ = (Node *) n;
}
+ | ALTER INDEX qualified_name index_alter_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 IF_P EXISTS qualified_name index_alter_cmd
+ {
+ AlterTableStmt *n = makeNode(AlterTableStmt);
+ n->relation = $5;
+ n->cmds = list_make1($6);
+ n->objtype = OBJECT_INDEX;
+ n->missing_ok = true;
+ $$ = (Node *) n;
+ }
| ALTER INDEX qualified_name alter_table_cmds
{
AlterTableStmt *n = makeNode(AlterTableStmt);
@@ -2369,6 +2388,21 @@ index_partition_cmd:
}
;
+index_alter_cmd:
+ /* ALTER INDEX <name> ENABLE|DISABLE */
+ ENABLE_P
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_EnableIndex;
+ $$ = (Node *) n;
+ }
+ | DISABLE_P
+ {
+ AlterTableCmd *n = makeNode(AlterTableCmd);
+ n->subtype = AT_DisableIndex;
+ $$ = (Node *) n;
+ }
+ ;
alter_table_cmd:
/* ALTER TABLE <name> ADD <coldef> */
ADD_P columnDef
@@ -8102,7 +8136,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_index_enabled
{
IndexStmt *n = makeNode(IndexStmt);
@@ -8117,6 +8151,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_single_name
n->options = $14;
n->tableSpace = $15;
n->whereClause = $16;
+ n->isenabled = $17;
n->excludeOpNames = NIL;
n->idxcomment = NULL;
n->indexOid = InvalidOid;
@@ -8134,7 +8169,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_index_enabled
{
IndexStmt *n = makeNode(IndexStmt);
@@ -8149,6 +8184,7 @@ IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_single_name
n->options = $17;
n->tableSpace = $18;
n->whereClause = $19;
+ n->isenabled = $20;
n->excludeOpNames = NIL;
n->idxcomment = NULL;
n->indexOid = InvalidOid;
@@ -8171,6 +8207,12 @@ opt_unique:
| /*EMPTY*/ { $$ = false; }
;
+opt_index_enabled:
+ ENABLE_P { $$ = true; }
+ | DISABLE_P { $$ = false; }
+ | /*EMPTY*/ { $$ = true; }
+ ;
+
access_method_clause:
USING name { $$ = $2; }
| /*EMPTY*/ { $$ = DEFAULT_INDEX_TYPE; }
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 1e15ce10b4..32554612ed 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1588,6 +1588,7 @@ generateClonedIndexStmt(RangeVar *heapRel, Relation source_idx,
index->concurrent = false;
index->if_not_exists = false;
index->reset_default_tblspc = false;
+ index->isenabled = idxrec->indisenabled;
/*
* We don't try to preserve the name of the source index; instead, just
@@ -2214,6 +2215,8 @@ transformIndexConstraint(Constraint *constraint, CreateStmtContext *cxt)
index->concurrent = false;
index->if_not_exists = false;
index->reset_default_tblspc = constraint->reset_default_tblspc;
+ /* Ensure indexes for constraints are created as enabled by default */
+ index->isenabled = true;
/*
* If it's ALTER TABLE ADD CONSTRAINT USING INDEX, look up the index and
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 2177d17e27..05b27ca232 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -1559,6 +1559,10 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
else
appendStringInfo(&buf, " WHERE %s", str);
}
+
+ /* Add DISABLE clause if the index is disabled */
+ if (!idxrec->indisenabled)
+ appendStringInfoString(&buf, " DISABLE");
}
/* Clean up */
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 5b6b7b809c..7cd5902ad7 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -2344,6 +2344,7 @@ RelationReloadIndexInfo(Relation relation)
relation->rd_index->indisready = index->indisready;
relation->rd_index->indislive = index->indislive;
relation->rd_index->indisreplident = index->indisreplident;
+ relation->rd_index->indisenabled = index->indisenabled;
/* Copy xmin too, as that is needed to make sense of indcheckxmin */
HeapTupleHeaderSetXmin(relation->rd_indextuple->t_data,
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 10bb26f2e4..a20af83da4 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -57,6 +57,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202409211
+#define CATALOG_VERSION_NO 202409220
#endif
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 2dea96f47c..8a47cfd174 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_ENABLED (1 << 7)
extern Oid index_create(Relation heapRelation,
const char *indexRelationName,
@@ -175,7 +176,6 @@ extern void RestoreReindexState(const void *reindexstate);
extern void IndexSetParentIndex(Relation partitionIdx, Oid parentOid);
-
/*
* itemptr_encode - Encode ItemPointer as int64/int8
*
diff --git a/src/include/catalog/pg_index.h b/src/include/catalog/pg_index.h
index 6c89639a9e..b0c7e5f365 100644
--- a/src/include/catalog/pg_index.h
+++ b/src/include/catalog/pg_index.h
@@ -44,6 +44,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 indisenabled; /* is this index enabled 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/parsenodes.h b/src/include/nodes/parsenodes.h
index e62ce1b753..e5125895b0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2413,6 +2413,8 @@ typedef enum AlterTableType
AT_SetIdentity, /* SET identity column options */
AT_DropIdentity, /* DROP IDENTITY */
AT_ReAddStatistics, /* internal to commands/tablecmds.c */
+ AT_EnableIndex, /* ENABLE INDEX */
+ AT_DisableIndex, /* DISABLE INDEX */
} AlterTableType;
typedef struct ReplicaIdentityStmt
@@ -3378,6 +3380,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 isenabled; /* true if ENABLE (default), false if DISABLE */
} IndexStmt;
/* ----------------------
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 07e2415398..4096c4b797 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -1188,6 +1188,8 @@ struct IndexOptInfo
bool immediate;
/* true if index doesn't really exist */
bool hypothetical;
+ /* is index enable? */
+ bool enabled;
/*
* Remaining fields are copied from the index AM's API struct
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index d3358dfc39..57add757aa 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -2965,6 +2965,256 @@ ERROR: REINDEX SCHEMA cannot run inside a transaction block
END;
-- concurrently
REINDEX SCHEMA CONCURRENTLY schema_to_reindex;
+-- Test enable/disable functionality for indexes
+-- Setup
+CREATE TABLE enable_disable_test(id int primary key, data text);
+INSERT INTO enable_disable_test SELECT g, 'data ' || g FROM generate_series(1, 1000) g;
+-- CREATE INDEX with ENABLED/DISABLED
+CREATE INDEX enable_disable_idx1 ON enable_disable_test(data) DISABLE;
+CREATE INDEX enable_disable_idx2 ON enable_disable_test(data);
+SELECT indexrelid::regclass, indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indrelid = 'enable_disable_test'::regclass
+ORDER BY indexrelid::regclass::text;
+ indexrelid | indisvalid | indisready | indislive | indisenabled
+--------------------------+------------+------------+-----------+--------------
+ enable_disable_idx1 | t | t | t | f
+ enable_disable_idx2 | t | t | t | t
+ enable_disable_test_pkey | t | t | t | t
+(3 rows)
+
+-- ALTER INDEX ... ENABLE/DISABLE
+-- Before
+SELECT indexrelid::regclass, indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indrelid = 'enable_disable_test'::regclass
+ORDER BY indexrelid::regclass::text;
+ indexrelid | indisvalid | indisready | indislive | indisenabled
+--------------------------+------------+------------+-----------+--------------
+ enable_disable_idx1 | t | t | t | f
+ enable_disable_idx2 | t | t | t | t
+ enable_disable_test_pkey | t | t | t | t
+(3 rows)
+
+ALTER INDEX enable_disable_idx2 DISABLE;
+NOTICE: index "enable_disable_idx2" is now disabled
+-- After
+SELECT indexrelid::regclass, indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indrelid = 'enable_disable_test'::regclass
+ORDER BY indexrelid::regclass::text;
+ indexrelid | indisvalid | indisready | indislive | indisenabled
+--------------------------+------------+------------+-----------+--------------
+ enable_disable_idx1 | t | t | t | f
+ enable_disable_idx2 | t | t | t | f
+ enable_disable_test_pkey | t | t | t | t
+(3 rows)
+
+-- Enable all indexes
+ALTER INDEX enable_disable_idx2 ENABLE;
+NOTICE: index "enable_disable_idx2" is now enabled
+ALTER INDEX enable_disable_idx1 ENABLE;
+NOTICE: index "enable_disable_idx1" is now enabled
+SELECT indexrelid::regclass, indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indrelid = 'enable_disable_test'::regclass
+ORDER BY indexrelid::regclass::text;
+ indexrelid | indisvalid | indisready | indislive | indisenabled
+--------------------------+------------+------------+-----------+--------------
+ enable_disable_idx1 | t | t | t | t
+ enable_disable_idx2 | t | t | t | t
+ enable_disable_test_pkey | t | t | t | t
+(3 rows)
+
+-- REINDEX TABLE
+REINDEX TABLE enable_disable_test;
+SELECT indexrelid::regclass, indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indrelid = 'enable_disable_test'::regclass
+ORDER BY indexrelid::regclass::text;
+ indexrelid | indisvalid | indisready | indislive | indisenabled
+--------------------------+------------+------------+-----------+--------------
+ enable_disable_idx1 | t | t | t | t
+ enable_disable_idx2 | t | t | t | t
+ enable_disable_test_pkey | t | t | t | t
+(3 rows)
+
+-- REINDEX INDEX with enable/disable
+ALTER INDEX enable_disable_idx1 DISABLE;
+NOTICE: index "enable_disable_idx1" is now disabled
+REINDEX INDEX enable_disable_idx1;
+SELECT indexrelid::regclass, indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indrelid = 'enable_disable_test'::regclass
+ORDER BY indexrelid::regclass::text;
+ indexrelid | indisvalid | indisready | indislive | indisenabled
+--------------------------+------------+------------+-----------+--------------
+ enable_disable_idx1 | t | t | t | f
+ enable_disable_idx2 | t | t | t | t
+ enable_disable_test_pkey | t | t | t | t
+(3 rows)
+
+-- REINDEX INDEX CONCURRENTLY with enable/disable
+ALTER INDEX enable_disable_idx1 ENABLE;
+NOTICE: index "enable_disable_idx1" is now enabled
+REINDEX INDEX enable_disable_idx1;
+SELECT indexrelid::regclass, indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indrelid = 'enable_disable_test'::regclass
+ORDER BY indexrelid::regclass::text;
+ indexrelid | indisvalid | indisready | indislive | indisenabled
+--------------------------+------------+------------+-----------+--------------
+ enable_disable_idx1 | t | t | t | t
+ enable_disable_idx2 | t | t | t | t
+ enable_disable_test_pkey | t | t | t | t
+(3 rows)
+
+-- Test ENABLE/DISABLE on TOAST index
+CREATE TABLE toast_test (id int primary key, data text);
+INSERT INTO toast_test SELECT g, repeat('long text ', 1000) FROM generate_series(1, 10) g;
+-- Check initial state of TOAST index
+SELECT indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indexrelid = (SELECT indexrelid FROM pg_index WHERE indrelid = (SELECT reltoastrelid FROM pg_class WHERE oid = 'toast_test'::regclass));
+ indisvalid | indisready | indislive | indisenabled
+------------+------------+-----------+--------------
+ t | t | t | t
+(1 row)
+
+-- Disable TOAST index
+ALTER INDEX pg_toast.pg_toast_16385_index DISABLE;
+ERROR: relation "pg_toast.pg_toast_16385_index" does not exist
+-- Check state after disabling TOAST index
+SELECT indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indexrelid = (SELECT indexrelid FROM pg_index WHERE indrelid = (SELECT reltoastrelid FROM pg_class WHERE oid = 'toast_test'::regclass));
+ indisvalid | indisready | indislive | indisenabled
+------------+------------+-----------+--------------
+ t | t | t | t
+(1 row)
+
+-- Enable TOAST index
+ALTER INDEX pg_toast.pg_toast_16385_index ENABLE;
+ERROR: relation "pg_toast.pg_toast_16385_index" does not exist
+-- Check state after enabling TOAST index
+SELECT indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indexrelid = (SELECT indexrelid FROM pg_index WHERE indrelid = (SELECT reltoastrelid FROM pg_class WHERE oid = 'toast_test'::regclass));
+ indisvalid | indisready | indislive | indisenabled
+------------+------------+-----------+--------------
+ t | t | t | t
+(1 row)
+
+-- Test CREATE TABLE with UNIQUE constraint
+CREATE TABLE unique_constraint_test (id int UNIQUE, data text);
+INSERT INTO unique_constraint_test SELECT g, 'data ' || g FROM generate_series(1, 1000) g;
+SELECT indexrelid::regclass, indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indrelid = 'unique_constraint_test'::regclass;
+ indexrelid | indisvalid | indisready | indislive | indisenabled
+-------------------------------+------------+------------+-----------+--------------
+ unique_constraint_test_id_key | t | t | t | t
+(1 row)
+
+-- Test that the unique constraint index is used
+EXPLAIN (COSTS OFF) SELECT * FROM unique_constraint_test WHERE id = 500;
+ QUERY PLAN
+--------------------------------------------------------------------------
+ Index Scan using unique_constraint_test_id_key on unique_constraint_test
+ Index Cond: (id = 500)
+(2 rows)
+
+EXPLAIN (COSTS OFF) SELECT * FROM unique_constraint_test WHERE id IN (100, 200, 300);
+ QUERY PLAN
+-------------------------------------------------------------
+ Bitmap Heap Scan on unique_constraint_test
+ Recheck Cond: (id = ANY ('{100,200,300}'::integer[]))
+ -> Bitmap Index Scan on unique_constraint_test_id_key
+ Index Cond: (id = ANY ('{100,200,300}'::integer[]))
+(4 rows)
+
+-- Test CREATE TABLE with INDEX
+CREATE TABLE index_test (id int, data text);
+INSERT INTO index_test SELECT g, 'data ' || g FROM generate_series(1, 1000) g;
+CREATE INDEX ON index_test (data);
+SELECT indexrelid::regclass, indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indrelid = 'index_test'::regclass;
+ indexrelid | indisvalid | indisready | indislive | indisenabled
+---------------------+------------+------------+-----------+--------------
+ index_test_data_idx | t | t | t | t
+(1 row)
+
+-- Test that the index is used
+EXPLAIN (COSTS OFF) SELECT * FROM index_test WHERE data = 'data 500';
+ QUERY PLAN
+------------------------------------------------
+ Bitmap Heap Scan on index_test
+ Recheck Cond: (data = 'data 500'::text)
+ -> Bitmap Index Scan on index_test_data_idx
+ Index Cond: (data = 'data 500'::text)
+(4 rows)
+
+-- Test index usage with joins
+CREATE TABLE join_test (id int PRIMARY KEY, ref_id int);
+INSERT INTO join_test SELECT g, g % 100 FROM generate_series(1, 1000) g;
+EXPLAIN (COSTS OFF)
+SELECT jt.id, it.data
+FROM join_test jt
+JOIN index_test it ON jt.ref_id = it.id
+WHERE jt.id BETWEEN 100 AND 200;
+ QUERY PLAN
+---------------------------------------------------------------
+ Hash Join
+ Hash Cond: (it.id = jt.ref_id)
+ -> Seq Scan on index_test it
+ -> Hash
+ -> Bitmap Heap Scan on join_test jt
+ Recheck Cond: ((id >= 100) AND (id <= 200))
+ -> Bitmap Index Scan on join_test_pkey
+ Index Cond: ((id >= 100) AND (id <= 200))
+(8 rows)
+
+-- Test index usage with ORDER BY
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM index_test
+ORDER BY data
+LIMIT 10;
+ QUERY PLAN
+----------------------------------------------------------
+ Limit
+ -> Index Scan using index_test_data_idx on index_test
+(2 rows)
+
+-- Test disabling an index and its effect on query plan
+ALTER INDEX index_test_data_idx DISABLE;
+NOTICE: index "index_test_data_idx" is now disabled
+EXPLAIN (COSTS OFF) SELECT * FROM index_test WHERE data = 'data 500';
+ QUERY PLAN
+-------------------------------------
+ Seq Scan on index_test
+ Filter: (data = 'data 500'::text)
+(2 rows)
+
+-- Re-enable the index
+ALTER INDEX index_test_data_idx ENABLE;
+NOTICE: index "index_test_data_idx" is now enabled
+EXPLAIN (COSTS OFF) SELECT * FROM index_test WHERE data = 'data 500';
+ QUERY PLAN
+------------------------------------------------
+ Bitmap Heap Scan on index_test
+ Recheck Cond: (data = 'data 500'::text)
+ -> Bitmap Index Scan on index_test_data_idx
+ Index Cond: (data = 'data 500'::text)
+(4 rows)
+
+-- Clean up
+DROP TABLE enable_disable_test;
+DROP TABLE toast_test;
+DROP TABLE unique_constraint_test;
+DROP TABLE join_test;
+DROP TABLE index_test;
-- Failure for unauthorized user
CREATE ROLE regress_reindexuser NOLOGIN;
SET SESSION ROLE regress_reindexuser;
diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql
index fe162cc7c3..d599717c6f 100644
--- a/src/test/regress/sql/create_index.sql
+++ b/src/test/regress/sql/create_index.sql
@@ -1297,6 +1297,146 @@ END;
-- concurrently
REINDEX SCHEMA CONCURRENTLY schema_to_reindex;
+-- Test enable/disable functionality for indexes
+
+-- Setup
+CREATE TABLE enable_disable_test(id int primary key, data text);
+INSERT INTO enable_disable_test SELECT g, 'data ' || g FROM generate_series(1, 1000) g;
+
+-- CREATE INDEX with ENABLED/DISABLED
+CREATE INDEX enable_disable_idx1 ON enable_disable_test(data) DISABLE;
+CREATE INDEX enable_disable_idx2 ON enable_disable_test(data);
+SELECT indexrelid::regclass, indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indrelid = 'enable_disable_test'::regclass
+ORDER BY indexrelid::regclass::text;
+
+-- ALTER INDEX ... ENABLE/DISABLE
+-- Before
+SELECT indexrelid::regclass, indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indrelid = 'enable_disable_test'::regclass
+ORDER BY indexrelid::regclass::text;
+
+ALTER INDEX enable_disable_idx2 DISABLE;
+-- After
+SELECT indexrelid::regclass, indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indrelid = 'enable_disable_test'::regclass
+ORDER BY indexrelid::regclass::text;
+
+-- Enable all indexes
+ALTER INDEX enable_disable_idx2 ENABLE;
+ALTER INDEX enable_disable_idx1 ENABLE;
+SELECT indexrelid::regclass, indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indrelid = 'enable_disable_test'::regclass
+ORDER BY indexrelid::regclass::text;
+
+-- REINDEX TABLE
+REINDEX TABLE enable_disable_test;
+SELECT indexrelid::regclass, indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indrelid = 'enable_disable_test'::regclass
+ORDER BY indexrelid::regclass::text;
+
+-- REINDEX INDEX with enable/disable
+ALTER INDEX enable_disable_idx1 DISABLE;
+REINDEX INDEX enable_disable_idx1;
+SELECT indexrelid::regclass, indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indrelid = 'enable_disable_test'::regclass
+ORDER BY indexrelid::regclass::text;
+
+-- REINDEX INDEX CONCURRENTLY with enable/disable
+ALTER INDEX enable_disable_idx1 ENABLE;
+REINDEX INDEX enable_disable_idx1;
+SELECT indexrelid::regclass, indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indrelid = 'enable_disable_test'::regclass
+ORDER BY indexrelid::regclass::text;
+
+-- Test ENABLE/DISABLE on TOAST index
+CREATE TABLE toast_test (id int primary key, data text);
+INSERT INTO toast_test SELECT g, repeat('long text ', 1000) FROM generate_series(1, 10) g;
+
+-- Check initial state of TOAST index
+SELECT indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indexrelid = (SELECT indexrelid FROM pg_index WHERE indrelid = (SELECT reltoastrelid FROM pg_class WHERE oid = 'toast_test'::regclass));
+
+-- Disable TOAST index
+ALTER INDEX pg_toast.pg_toast_16385_index DISABLE;
+
+-- Check state after disabling TOAST index
+SELECT indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indexrelid = (SELECT indexrelid FROM pg_index WHERE indrelid = (SELECT reltoastrelid FROM pg_class WHERE oid = 'toast_test'::regclass));
+
+-- Enable TOAST index
+ALTER INDEX pg_toast.pg_toast_16385_index ENABLE;
+
+-- Check state after enabling TOAST index
+SELECT indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indexrelid = (SELECT indexrelid FROM pg_index WHERE indrelid = (SELECT reltoastrelid FROM pg_class WHERE oid = 'toast_test'::regclass));
+
+-- Test CREATE TABLE with UNIQUE constraint
+CREATE TABLE unique_constraint_test (id int UNIQUE, data text);
+INSERT INTO unique_constraint_test SELECT g, 'data ' || g FROM generate_series(1, 1000) g;
+SELECT indexrelid::regclass, indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indrelid = 'unique_constraint_test'::regclass;
+
+-- Test that the unique constraint index is used
+EXPLAIN (COSTS OFF) SELECT * FROM unique_constraint_test WHERE id = 500;
+EXPLAIN (COSTS OFF) SELECT * FROM unique_constraint_test WHERE id IN (100, 200, 300);
+
+-- Test CREATE TABLE with INDEX
+CREATE TABLE index_test (id int, data text);
+INSERT INTO index_test SELECT g, 'data ' || g FROM generate_series(1, 1000) g;
+CREATE INDEX ON index_test (data);
+SELECT indexrelid::regclass, indisvalid, indisready, indislive, indisenabled
+FROM pg_index
+WHERE indrelid = 'index_test'::regclass;
+
+-- Test that the index is used
+EXPLAIN (COSTS OFF) SELECT * FROM index_test WHERE data = 'data 500';
+
+-- Test index usage with joins
+CREATE TABLE join_test (id int PRIMARY KEY, ref_id int);
+INSERT INTO join_test SELECT g, g % 100 FROM generate_series(1, 1000) g;
+
+EXPLAIN (COSTS OFF)
+SELECT jt.id, it.data
+FROM join_test jt
+JOIN index_test it ON jt.ref_id = it.id
+WHERE jt.id BETWEEN 100 AND 200;
+
+-- Test index usage with ORDER BY
+EXPLAIN (COSTS OFF)
+SELECT *
+FROM index_test
+ORDER BY data
+LIMIT 10;
+
+-- Test disabling an index and its effect on query plan
+ALTER INDEX index_test_data_idx DISABLE;
+
+EXPLAIN (COSTS OFF) SELECT * FROM index_test WHERE data = 'data 500';
+
+-- Re-enable the index
+ALTER INDEX index_test_data_idx ENABLE;
+
+EXPLAIN (COSTS OFF) SELECT * FROM index_test WHERE data = 'data 500';
+
+-- Clean up
+DROP TABLE enable_disable_test;
+DROP TABLE toast_test;
+DROP TABLE unique_constraint_test;
+DROP TABLE join_test;
+DROP TABLE index_test;
+
-- Failure for unauthorized user
CREATE ROLE regress_reindexuser NOLOGIN;
SET SESSION ROLE regress_reindexuser;
--
2.37.1 (Apple Git-137.1)