0001-Validate-parent-partitioned-index-after-REINDEX.patch
application/octet-stream
Filename: 0001-Validate-parent-partitioned-index-after-REINDEX.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 0001
Subject: fix: Validate parent partitioned index after REINDEX of child
| File | + | − |
|---|---|---|
| src/backend/catalog/index.c | 32 | 0 |
| src/backend/commands/indexcmds.c | 29 | 0 |
| src/backend/commands/tablecmds.c | 6 | 3 |
| src/include/commands/tablecmds.h | 2 | 0 |
From 50da7cad2cd0f71c669b364d7eb682d71c143491 Mon Sep 17 00:00:00 2001
From: Mohamed Ali <moali.pg@gmail.com>
Date: Fri, 27 Mar 2026 19:38:32 -0700
Subject: [PATCH] fix: Validate parent partitioned index after REINDEX of child
After REINDEX (or REINDEX CONCURRENTLY) repairs an invalid partition
index, the parent partitioned index remains permanently stuck with
indisvalid=false because validatePartitionedIndex() is never called
from any REINDEX code path.
Fix this by:
1. Making validatePartitionedIndex() non-static and adding its
declaration to tablecmds.h so it can be called from other modules.
2. Calling validatePartitionedIndex() from reindex_index() (for
regular REINDEX) after marking a partition index valid. A
CommandCounterIncrement() is needed before the call so that the
child's updated indisvalid is visible to the syscache lookup
performed by validatePartitionedIndex().
3. Calling validatePartitionedIndex() from
ReindexRelationConcurrently() (for REINDEX CONCURRENTLY) after
the index swap and CommandCounterIncrement, at which point the
new valid index has taken the old invalid index's place in the
partition hierarchy.
validatePartitionedIndex() already handles multi-level partition
hierarchies by recursing upward when it marks a mid-level parent
valid, so this fix automatically cascades through grandparent and
higher-level partitioned indexes.
---
src/backend/catalog/index.c | 32 ++++++++++++++++++++++++++++++++
src/backend/commands/indexcmds.c | 29 +++++++++++++++++++++++++++++
src/backend/commands/tablecmds.c | 9 ++++++---
src/include/commands/tablecmds.h | 2 ++
4 files changed, 69 insertions(+), 3 deletions(-)
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index d8219b1..bf1a25c 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3885,6 +3885,38 @@ reindex_index(const ReindexStmt *stmt, Oid indexId,
CacheInvalidateRelcache(heapRelation);
}
+ /*
+ * If this index is a partition of a partitioned index, and we just
+ * marked it valid, check if the parent partitioned index can now be
+ * marked valid too. This handles the case where an invalid partition
+ * index was attached to a partitioned index (making the parent
+ * invalid), then later fixed via REINDEX. validatePartitionedIndex
+ * will recurse up the hierarchy if needed.
+ */
+ if (index_bad && iRel->rd_rel->relispartition)
+ {
+ Oid parentIdxId;
+
+ /* Make the child's indisvalid update visible for validation */
+ CommandCounterIncrement();
+
+ parentIdxId = get_partition_parent(indexId, false);
+ if (OidIsValid(parentIdxId))
+ {
+ Relation parentIdx;
+ Relation parentTbl;
+
+ parentIdx = index_open(parentIdxId, AccessShareLock);
+ parentTbl = table_open(parentIdx->rd_index->indrelid,
+ AccessShareLock);
+
+ validatePartitionedIndex(parentIdx, parentTbl);
+
+ table_close(parentTbl, AccessShareLock);
+ index_close(parentIdx, AccessShareLock);
+ }
+ }
+
table_close(pg_index, RowExclusiveLock);
}
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index dd593cc..ab677d7 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -34,6 +34,7 @@
#include "catalog/pg_constraint.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
+#include "catalog/partition.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_tablespace.h"
@@ -4317,6 +4318,34 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
* characters.
*/
CommandCounterIncrement();
+
+ /*
+ * If the new (swapped-in) index is a partition of a partitioned
+ * index, check if the parent can now be marked valid. This handles
+ * REINDEX CONCURRENTLY on a partition index that was previously
+ * invalid: the new (valid) index replaces the old (invalid) one,
+ * and the parent should be re-validated.
+ */
+ if (get_rel_relispartition(newidx->indexId))
+ {
+ Oid parentIdxId;
+
+ parentIdxId = get_partition_parent(newidx->indexId, true);
+ if (OidIsValid(parentIdxId))
+ {
+ Relation parentIdx;
+ Relation parentTbl;
+
+ parentIdx = index_open(parentIdxId, AccessShareLock);
+ parentTbl = table_open(parentIdx->rd_index->indrelid,
+ AccessShareLock);
+
+ validatePartitionedIndex(parentIdx, parentTbl);
+
+ table_close(parentTbl, AccessShareLock);
+ index_close(parentIdx, AccessShareLock);
+ }
+ }
}
/* Commit this transaction and make index swaps visible */
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c69c12d..8612067 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -746,7 +746,6 @@ static void DetachPartitionFinalize(Relation rel, Relation partRel,
static ObjectAddress ATExecDetachPartitionFinalize(Relation rel, RangeVar *name);
static ObjectAddress ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx,
RangeVar *name);
-static void validatePartitionedIndex(Relation partedIdx, Relation partedTbl);
static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
Relation partitionTbl);
static void verifyPartitionIndexNotNull(IndexInfo *iinfo, Relation partition);
@@ -22058,12 +22057,16 @@ refuseDupeIndexAttach(Relation parentIdx, Relation partIdx, Relation partitionTb
}
/*
+ * validatePartitionedIndex
+ *
* Verify whether the set of attached partition indexes to a parent index on
* a partitioned table is complete. If it is, mark the parent index valid.
*
- * This should be called each time a partition index is attached.
+ * This should be called each time a partition index is attached, and also
+ * after a partition index is repaired via REINDEX, so that the parent can
+ * be marked valid once all children are valid.
*/
-static void
+void
validatePartitionedIndex(Relation partedIdx, Relation partedTbl)
{
Relation inheritsRel;
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index c3d8518..84983fb 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -108,4 +108,6 @@ extern void RangeVarCallbackOwnsRelation(const RangeVar *relation,
extern bool PartConstraintImpliedByRelConstraint(Relation scanrel,
List *partConstraint);
+extern void validatePartitionedIndex(Relation partedIdx, Relation partedTbl);
+
#endif /* TABLECMDS_H */
--
2.50.1 (Apple Git-155)