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
Message: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired

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)