From 8587ddec3d498afc065e58cd444b3f24acc6dbd6 Mon Sep 17 00:00:00 2001 From: roman khapov Date: Tue, 30 Jun 2026 15:03:55 +0500 Subject: [PATCH] Add DROP INVALID INDEXES ON TABLE command Automates removal of invalid indexes that can appear after failed CREATE INDEX CONCURRENTLY operations or etc. Previously required manual queries against pg_index, which was error-prone. Uses ShareUpdateExclusiveLock to minimize impact on concurrent operations. Checks indisvalid and indisready flags to avoid dropping indexes still being created. Example of usage: postgres=# drop invalid indexes on table sas; NOTICE: dropping index "idx2" NOTICE: dropping index "idx5" DROP INVALID INDEXES postgres=# Currently without CONCURRENCY mode support. Co-authored-by: Kirill Reshke Signed-off-by: roman khapov --- src/backend/commands/tablecmds.c | 81 ++++++++++++++++++++++++++++++++ src/backend/parser/gram.y | 35 +++++++++++++- src/backend/tcop/utility.c | 38 +++++++++++++++ src/bin/psql/tab-complete.in.c | 12 +++++ src/include/commands/tablecmds.h | 2 + src/include/nodes/parsenodes.h | 13 +++++ src/include/parser/kwlist.h | 1 + src/include/tcop/cmdtaglist.h | 1 + src/tools/pgindent/typedefs.list | 1 + 9 files changed, 182 insertions(+), 2 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 472db112fa7..8f283633786 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -1587,6 +1587,87 @@ DropErrorMsgWrongType(const char *relname, char wrongkind, char rightkind) (wentry->kind != '\0') ? errhint("%s", _(wentry->drophint_msg)) : 0)); } +void +RemoveInvalidIndexes(DropInvalidIndexesStmt *drop) +{ + ObjectAddresses *indexesToDrop; + List *indexesList; + ListCell *cell; + Relation relation; + Oid relOid; + ObjectAddress obj; + RangeVar *rel; + LOCKMODE lockmode = ShareUpdateExclusiveLock; + int flags = 0; + struct DropRelationCallbackState state; + + if (drop->concurrent) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("DROP INVALID INDEXES CONCURRENTLY is not supported"))); + + if (list_length(drop->tablenames) != 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("DROP INVALID INDEXES is not supported for multiple table now"))); + + indexesToDrop = new_object_addresses(); + + cell = list_head(drop->tablenames); + + rel = makeRangeVarFromNameList((List *) lfirst(cell)); + state.expected_relkind = RELKIND_RELATION; + /* Use ShareUpdateExclusiveLock if implementing concurrent */ + state.heap_lockmode = AccessExclusiveLock; + state.heapOid = InvalidOid; + state.partParentOid = InvalidOid; + + relOid = RangeVarGetRelidExtended(rel, lockmode, RVR_MISSING_OK, + RangeVarCallbackForDropRelation, + &state); + + if (!OidIsValid(relOid)) + { + DropErrorMsgNonExistent(rel, RELKIND_RELATION, + false /* missing is not ok now */ ); + return; + } + + relation = table_open(relOid, lockmode); + indexesList = RelationGetIndexList(relation); + + foreach(cell, indexesList) + { + Oid indexoid = lfirst_oid(cell); + Relation indrel; + + indrel = index_open(indexoid, AccessExclusiveLock); + + if (!indrel->rd_index->indisvalid) + { + ereport(NOTICE, + (errmsg("dropping index \"%s\"", + RelationGetRelationName(indrel)))); + + obj.classId = RelationRelationId; + obj.objectId = indexoid; + obj.objectSubId = 0; + + add_exact_object_address(&obj, indexesToDrop); + } + + index_close(indrel, AccessExclusiveLock); + } + + table_close(relation, lockmode); + + list_free(indexesList); + + performMultipleDeletions(indexesToDrop, drop->behavior, flags); + + free_object_addresses(indexesToDrop); +} + /* * RemoveRelations * Implements DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW, diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index ff4e1388c55..b8d0ebd18d3 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -298,7 +298,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); CreatePropGraphStmt AlterPropGraphStmt CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePolicyStmt CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt - DropOpClassStmt DropOpFamilyStmt DropStmt + DropInvalidIndexesStmt DropOpClassStmt DropOpFamilyStmt DropStmt DropCastStmt DropRoleStmt DropdbStmt DropTableSpaceStmt DropTransformStmt @@ -778,7 +778,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); IDENTITY_P IF_P IGNORE_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLUDE INCLUDING INCREMENT INDENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER - INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION + INTERSECT INTERVAL INTO INVALID_P INVOKER IS ISNULL ISOLATION JOIN JSON JSON_ARRAY JSON_ARRAYAGG JSON_EXISTS JSON_OBJECT JSON_OBJECTAGG JSON_QUERY JSON_SCALAR JSON_SERIALIZE JSON_TABLE JSON_VALUE @@ -1118,6 +1118,7 @@ stmt: | DiscardStmt | DoStmt | DropCastStmt + | DropInvalidIndexesStmt | DropOpClassStmt | DropOpFamilyStmt | DropOwnedStmt @@ -7100,6 +7101,35 @@ ReassignOwnedStmt: } ; +/***************************************************************************** + * + * QUERY: + * + * DROP INVALID INDEXES [ CONCURRENTLY ] ON TABLE tablename [, tablename ...] [ RESTRICT | CASCADE ] + * + *****************************************************************************/ + +DropInvalidIndexesStmt: + DROP INVALID_P INDEXES ON TABLE any_name_list opt_drop_behavior + { + DropInvalidIndexesStmt *n = makeNode(DropInvalidIndexesStmt); + + n->tablenames = $6; + n->behavior = $7; + n->concurrent = false; + $$ = (Node *) n; + } + | DROP INVALID_P INDEXES CONCURRENTLY ON TABLE any_name_list opt_drop_behavior + { + DropInvalidIndexesStmt *n = makeNode(DropInvalidIndexesStmt); + + n->tablenames = $7; + n->behavior = $8; + n->concurrent = true; + $$ = (Node *) n; + } + ; + /***************************************************************************** * * QUERY: @@ -18958,6 +18988,7 @@ unreserved_keyword: | INSENSITIVE | INSERT | INSTEAD + | INVALID_P | INVOKER | ISOLATION | KEEP diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 73a56f1df1d..33cb6978b0e 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -82,6 +82,7 @@ static void ProcessUtilitySlow(ParseState *pstate, DestReceiver *dest, QueryCompletion *qc); static void ExecDropStmt(DropStmt *stmt, bool isTopLevel); +static void ExecDropInvalidIndexes(DropInvalidIndexesStmt *stmt, bool isTopLevel); /* * CommandIsReadOnly: is an executable query read-only? @@ -200,6 +201,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree) case T_DropOwnedStmt: case T_DropRoleStmt: case T_DropStmt: + case T_DropInvalidIndexesStmt: case T_DropSubscriptionStmt: case T_DropTableSpaceStmt: case T_DropUserMappingStmt: @@ -969,6 +971,19 @@ standard_ProcessUtility(PlannedStmt *pstmt, } break; + case T_DropInvalidIndexesStmt: + { + DropInvalidIndexesStmt *stmt = (DropInvalidIndexesStmt *) parsetree; + + if (EventTriggerSupportsObjectType(OBJECT_INDEX)) + ProcessUtilitySlow(pstate, pstmt, queryString, + context, params, queryEnv, + dest, qc); + else + ExecDropInvalidIndexes(stmt, isTopLevel); + } + break; + case T_DropStmt: { DropStmt *stmt = (DropStmt *) parsetree; @@ -1782,6 +1797,12 @@ ProcessUtilitySlow(ParseState *pstate, commandCollected = true; break; + case T_DropInvalidIndexesStmt: + ExecDropInvalidIndexes((DropInvalidIndexesStmt *) parsetree, isTopLevel); + /* no commands stashed for DROP */ + commandCollected = true; + break; + case T_DropStmt: ExecDropStmt((DropStmt *) parsetree, isTopLevel); /* no commands stashed for DROP */ @@ -2029,6 +2050,15 @@ ExecDropStmt(DropStmt *stmt, bool isTopLevel) } } +static void +ExecDropInvalidIndexes(DropInvalidIndexesStmt *stmt, bool isTopLevel) +{ + if (stmt->concurrent) + PreventInTransactionBlock(isTopLevel, + "DROP INVALID INDEXES CONCURRENTLY"); + + RemoveInvalidIndexes(stmt); +} /* * UtilityReturnsTuples @@ -2564,6 +2594,10 @@ CreateCommandTag(Node *parsetree) tag = CMDTAG_IMPORT_FOREIGN_SCHEMA; break; + case T_DropInvalidIndexesStmt: + tag = CMDTAG_DROP_INVALID_INDEXES; + break; + case T_DropStmt: switch (((DropStmt *) parsetree)->removeType) { @@ -3523,6 +3557,10 @@ GetCommandLogLevel(Node *parsetree) lev = LOGSTMT_DDL; break; + case T_DropInvalidIndexesStmt: + lev = LOGSTMT_DDL; + break; + case T_DropdbStmt: lev = LOGSTMT_DDL; break; diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c index 46b9add0604..897e9609c24 100644 --- a/src/bin/psql/tab-complete.in.c +++ b/src/bin/psql/tab-complete.in.c @@ -1335,6 +1335,7 @@ static const pgsql_thing_t words_after_create[] = { {"FUNCTION", NULL, NULL, Query_for_list_of_functions}, {"GROUP", Query_for_list_of_roles}, {"INDEX", NULL, NULL, &Query_for_list_of_indexes}, + {"INVALID", NULL, NULL, NULL, NULL, THING_NO_CREATE | THING_NO_ALTER}, {"LANGUAGE", Query_for_list_of_languages}, {"LARGE OBJECT", NULL, NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP}, {"MATERIALIZED VIEW", NULL, NULL, &Query_for_list_of_matviews}, @@ -4430,6 +4431,17 @@ match_previous_words(int pattern_id, else if (Matches("DROP", "INDEX", "CONCURRENTLY", MatchAny)) COMPLETE_WITH("CASCADE", "RESTRICT"); + /* DROP INVALID INDEXES */ + else if (Matches("DROP", "INVALID")) + COMPLETE_WITH("INDEXES ON TABLE"); + else if (Matches("DROP", "INVALID", "INDEXES")) + COMPLETE_WITH("ON TABLE"); + else if (Matches("DROP", "INVALID", "INDEXES", "ON")) + COMPLETE_WITH("TABLE"); + else if (Matches("DROP", "INVALID", "INDEXES", "ON", "TABLE")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tables); + + /* DROP MATERIALIZED VIEW */ else if (Matches("DROP", "MATERIALIZED")) COMPLETE_WITH("VIEW"); diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index c3d8518cb62..70dff275d4b 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -32,6 +32,8 @@ extern TupleDesc BuildDescForRelation(const List *columns); extern void RemoveRelations(DropStmt *drop); +extern void RemoveInvalidIndexes(DropInvalidIndexesStmt *drop); + extern Oid AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode); extern void AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 4133c404a6b..43cbabc77af 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -3444,6 +3444,19 @@ typedef struct DropStmt bool concurrent; /* drop index concurrently? */ } DropStmt; +/* ---------------------- + * Drop invalid indexes on table Statement + * ---------------------- + */ + +typedef struct DropInvalidIndexesStmt +{ + NodeTag type; + List *tablenames; /* list of table names */ + DropBehavior behavior; /* RESTRICT or CASCADE behavior */ + bool concurrent; /* drop index concurrently? */ +} DropInvalidIndexesStmt; + /* ---------------------- * Truncate Table Statement * ---------------------- diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 51ead54f015..7754c7e6304 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -234,6 +234,7 @@ PG_KEYWORD("integer", INTEGER, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("intersect", INTERSECT, RESERVED_KEYWORD, AS_LABEL) PG_KEYWORD("interval", INTERVAL, COL_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("into", INTO, RESERVED_KEYWORD, AS_LABEL) +PG_KEYWORD("invalid", INVALID_P, UNRESERVED_KEYWORD, AS_LABEL) PG_KEYWORD("invoker", INVOKER, UNRESERVED_KEYWORD, BARE_LABEL) PG_KEYWORD("is", IS, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL) PG_KEYWORD("isnull", ISNULL, TYPE_FUNC_NAME_KEYWORD, AS_LABEL) diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h index befae5f6b4f..3080208045b 100644 --- a/src/include/tcop/cmdtaglist.h +++ b/src/include/tcop/cmdtaglist.h @@ -150,6 +150,7 @@ PG_CMDTAG(CMDTAG_DROP_FOREIGN_DATA_WRAPPER, "DROP FOREIGN DATA WRAPPER", true, f PG_CMDTAG(CMDTAG_DROP_FOREIGN_TABLE, "DROP FOREIGN TABLE", true, false, false) PG_CMDTAG(CMDTAG_DROP_FUNCTION, "DROP FUNCTION", true, false, false) PG_CMDTAG(CMDTAG_DROP_INDEX, "DROP INDEX", true, false, false) +PG_CMDTAG(CMDTAG_DROP_INVALID_INDEXES, "DROP INVALID INDEXES", true, false, false) PG_CMDTAG(CMDTAG_DROP_LANGUAGE, "DROP LANGUAGE", true, false, false) PG_CMDTAG(CMDTAG_DROP_MATERIALIZED_VIEW, "DROP MATERIALIZED VIEW", true, false, false) PG_CMDTAG(CMDTAG_DROP_OPERATOR, "DROP OPERATOR", true, false, false) diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index c5db6ca6705..d7e6a521231 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -706,6 +706,7 @@ DropSubscriptionStmt DropTableSpaceStmt DropUserMappingStmt DropdbStmt +DropInvalidIndexesStmt DumpComponents DumpId DumpOptions -- 2.50.1 (Apple Git-155)