public inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired
12+ messages / 4 participants
[nested] [flat]

* [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired
@ 2026-04-09 06:16 Mohamed ALi <[email protected]>
  2026-04-11 00:55 ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Haibo Yan <[email protected]>
  0 siblings, 1 reply; 12+ messages in thread

From: Mohamed ALi @ 2026-04-09 06:16 UTC (permalink / raw)
  To: [email protected]

Hi hackers,

A partitioned (parent) index in PostgreSQL can become permanently
stuck with `indisvalid = false` even after all of its child partition
indexes have been repaired and are valid. There is no built-in
mechanism to re-validate the parent index after a child is fixed via
`REINDEX`. This affects all currently supported PostgreSQL versions
(13 through 18)
The root cause is that `validatePartitionedIndex()` — the only
function that can mark a partitioned index as valid is never called
after `REINDEX` operations, and is skipped when re-running `ALTER
INDEX ATTACH PARTITION` on an already-attached index.

How the Bug Manifests

Typical Scenario :
1. A partitioned table has multiple partitions.
2. The user creates indexes on partitions concurrently. One fails (due
to deadlock, cancellation, timeout, etc.), leaving an invalid
partition index.
3. A parent index is created (or the invalid index is attached to an
existing parent). The parent is correctly marked `indisvalid = false`
because at least one child is invalid.
4. The user fixes the broken child index with `REINDEX INDEX CONCURRENTLY`.
5. The child index becomes valid (`indisvalid = true`).
6. The parent index remains `indisvalid = false` permanently. No SQL
command can fix it.

Reproduction steps:

```sql
-- ============================================================
-- SETUP: Partitioned table with two partitions and sample data
-- ============================================================
DROP TABLE IF EXISTS orders;
CREATE TABLE orders (
    id serial,
    order_date date NOT NULL,
    amount numeric
) PARTITION BY RANGE (order_date);
CREATE TABLE orders_2023 PARTITION OF orders
    FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');
CREATE TABLE orders_2024 PARTITION OF orders
    FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
INSERT INTO orders (order_date, amount)
SELECT d, random() * 1000
FROM generate_series('2023-01-01'::date, '2023-12-31'::date, '1 day') d;
INSERT INTO orders (order_date, amount)
SELECT d, random() * 1000
FROM generate_series('2024-01-01'::date, '2024-12-31'::date, '1 day') d;
-- ============================================================
-- STEP 1: Create parent index with ONLY (starts as invalid)
-- ============================================================
CREATE INDEX orders_amount_idx ON ONLY orders (amount);
-- Verify: parent index is invalid (no children attached yet)
SELECT c.relname, i.indisvalid
FROM pg_class c
JOIN pg_index i ON c.oid = i.indexrelid
WHERE c.relname LIKE 'orders%idx%'
ORDER BY c.relname;
-- Expected:
--  orders_amount_idx | f
-- ============================================================
-- STEP 2: Create valid index on first partition
-- ============================================================
CREATE INDEX CONCURRENTLY orders_2023_amount_idx ON orders_2023 (amount);
-- ============================================================
-- STEP 3: Create an INVALID index on second partition
-- ============================================================
-- In a separate session, hold a lock:
BEGIN; LOCK TABLE orders_2024 IN SHARE MODE;
-- Then in the main session:
SET statement_timeout = '1ms';
CREATE INDEX CONCURRENTLY orders_2024_amount_idx ON orders_2024 (amount);
RESET statement_timeout;
-- it will fail/timeout, leaving an invalid index.
-- Verify state:
SELECT c.relname, i.indisvalid
FROM pg_class c
JOIN pg_index i ON c.oid = i.indexrelid
WHERE c.relname LIKE 'orders%idx%'
ORDER BY c.relname;
-- Expected:
--  orders_2023_amount_idx | t   (valid)
--  orders_2024_amount_idx | f   (invalid)
--  orders_amount_idx      | f   (invalid, created with ONLY)
-- ============================================================
-- STEP 4: Attach both partition indexes to the parent
-- ============================================================
-- Attach the invalid one first
ALTER INDEX orders_amount_idx ATTACH PARTITION orders_2024_amount_idx;
-- Succeeds. Parent stays invalid (correct — child is invalid).
-- Attach the valid one
ALTER INDEX orders_amount_idx ATTACH PARTITION orders_2023_amount_idx;
-- Succeeds. Parent still invalid (correct — one child still invalid).
-- Verify attachment and validity:
SELECT c.relname, i.indisvalid,
       pg_get_indexdef(i.indexrelid) AS indexdef
FROM pg_class c
JOIN pg_index i ON c.oid = i.indexrelid
WHERE c.relname LIKE 'orders%amount%'
ORDER BY c.relname;
-- Expected:
--  orders_2023_amount_idx | t
--  orders_2024_amount_idx | f
--  orders_amount_idx      | f
-- ============================================================
-- STEP 5: Fix the invalid child index via REINDEX
-- ============================================================
REINDEX INDEX CONCURRENTLY orders_2024_amount_idx;
-- Verify: child is now valid
SELECT c.relname, i.indisvalid
FROM pg_class c
JOIN pg_index i ON c.oid = i.indexrelid
WHERE c.relname LIKE 'orders%amount%'
ORDER BY c.relname;
-- ACTUAL (buggy) result:
--  orders_2023_amount_idx | t   (valid)
--  orders_2024_amount_idx | t   (valid — fixed by REINDEX)
--  orders_amount_idx      | f   (STILL INVALID — this is the bug!)
--
-- EXPECTED result (if bug were fixed):
--  orders_2023_amount_idx | t
--  orders_2024_amount_idx | t
--  orders_amount_idx      | t   (should be valid now)
-- ============================================================
-- STEP 6: Demonstrate that re-running ATTACH does not help
-- ============================================================
ALTER INDEX orders_amount_idx ATTACH PARTITION orders_2024_amount_idx;
-- Returns "ALTER INDEX" (succeeds silently, does nothing)
SELECT c.relname, i.indisvalid
FROM pg_class c
JOIN pg_index i ON c.oid = i.indexrelid
WHERE c.relname LIKE 'orders%amount%'
ORDER BY c.relname;
-- Parent is STILL invalid. The "silently do nothing" path
-- skips validatePartitionedIndex() entirely.
-- ============================================================
-- CLEANUP
-- ============================================================
DROP TABLE orders;
```


Root Cause Analysis:

Where `validatePartitionedIndex()` Is Called

The function is called in exactly these code paths:
1. During `ALTER INDEX ... ATTACH PARTITION` — inside
`ATExecAttachPartitionIdx()`
2. During `ALTER TABLE ... ATTACH PARTITION` — via
`AttachPartitionEnsureIndexes()`
3. During `CREATE INDEX` on partitioned tables — via `DefineIndex()`
It is NOT called:
- After `REINDEX` of a partitioned index
- During any maintenance operation
- As any periodic validation check

Bug 1: REINDEX Does Not Validate Parent


When `reindex_index()` in `src/backend/catalog/index.c` marks a
partition index as valid (setting `indisvalid = true`), it does not
check whether the parent partitioned index should also become valid.
The function simply updates the child's `pg_index` entry and returns.

Bug 2: Re-running ATTACH Skips Validation


In `ATExecAttachPartitionIdx()` (tablecmds.c, around line 21923 in PG
16 / line ~22900 in HEAD):
https://github.com/postgres/postgres/blob/master/src/backend/commands/tablecmds.c#L21923

```c
/* Silently do nothing if already in the right state */
currParent = partIdx->rd_rel->relispartition ?
    get_partition_parent(partIdxId, false) : InvalidOid;
if (currParent != RelationGetRelid(parentIdx))
{
    // ... all validation checks and attachment logic ...
    validatePartitionedIndex(parentIdx, parentTbl);  // ONLY called here
}
// If already attached, entire block is skipped — no validation!
```

When the child is already attached (`currParent == parentIdx`), the
condition is false, the entire if-block is skipped, and
`validatePartitionedIndex()` is never called. The comment "Silently do
nothing if already in the right state" is misleading  "already
attached" does not mean "parent validity is correct."

Proposed Fixes:

Fix 1 : Always Validate Parent Index in ALTER INDEX ATTACH PARTITION

Patch File : 0001-Always-validate-parent-index-in-ALTER-INDEX-ATTACH.patch

Move the validatePartitionedIndex() call outside the if-block so it runs
unconditionally — both when a new attachment is made and when the partition is
already attached. This provides a user-accessible recovery path: after fixing a
child index with REINDEX, re-running ALTER INDEX ATTACH PARTITION triggers
parent validation.

When the partition is already attached, a NOTICE is emitted:

NOTICE:  partition index "child_idx" is already attached to
"parent_idx", validating parent index


This follows PostgreSQL's existing convention of using NOTICE for
informational messages about no-op or reduced-scope operations (e.g.,
DROP TABLE IF EXISTS, CREATE INDEX IF NOT EXISTS). It tells the user:

1- Nothing went wrong
2- The index was already attached (so they know the state)
3-  Validation still happened (so they know the fix path works)


Fix 2: Validate Parent Partitioned Index After REINDEX of Child

Patch File : 0001-Validate-parent-partitioned-index-after-REINDEX.patch

Same underlying bug but this patch addresses it from the
REINDEX side. When a partition index is repaired via REINDEX or
REINDEX CONCURRENTLY, the parent partitioned index remains permanently
stuck with indisvalid = false even though all children are now valid.

This is because validatePartitionedIndex() — the only function that can
mark a partitioned index as valid is never called from any REINDEX code
path.


validatePartitionedIndex() is only called during:

1- ALTER INDEX ... ATTACH PARTITION (tablecmds.c)
2- ALTER TABLE ... ATTACH PARTITION (tablecmds.c)
3- CREATE INDEX on partitioned tables (indexcmds.c)

It is NOT called after:

1- REINDEX INDEX (regular) — handled by reindex_index() in index.c
2- REINDEX INDEX CONCURRENTLY — handled by ReindexRelationConcurrently()

in indexcmds.c, which uses index_concurrently_swap() in index.c

Three changes are made:

1. Make validatePartitionedIndex() public
The function was static in tablecmds.c. It is now exported via
tablecmds.h so it can be called from index.c and indexcmds.c.

Files changed:

src/backend/commands/tablecmds.c — remove static, update comment
src/include/commands/tablecmds.h — add extern declaration

2. Call from reindex_index() (regular REINDEX)
After reindex_index() marks a partition index as valid (indisvalid = true),
check if the index is a partition (iRel->rd_rel->relispartition) and if so,
look up the parent and call validatePartitionedIndex().

A CommandCounterIncrement() is required before the call so that the child's
updated indisvalid is visible to the syscache lookup that
validatePartitionedIndex() performs internally.

File changed: src/backend/catalog/index.c

3. Call from ReindexRelationConcurrently() (REINDEX CONCURRENTLY)
REINDEX CONCURRENTLY uses a completely different code path: it creates a new
index, builds it concurrently, then swaps it with the old one via
index_concurrently_swap(). The new index inherits the old index's partition
status during the swap.

After the swap and the existing CommandCounterIncrement() (which makes the
swap visible), check if the new index is a partition and call
validatePartitionedIndex() on the parent.

File changed: src/backend/commands/indexcmds.c

Multi-level Hierarchy Support
validatePartitionedIndex() already handles multi-level partition hierarchies.
When it marks a mid-level parent valid, it checks if that parent is itself a
partition and recursively validates the grandparent. No additional recursion
logic is needed in the REINDEX patches.


Thanks,
Mohamed Ali
Senior DBE
AWS RDS


Attachments:

  [application/octet-stream] 0001-Validate-parent-partitioned-index-after-REINDEX.patch (6.4K, 2-0001-Validate-parent-partitioned-index-after-REINDEX.patch)
  download | inline diff:
From 50da7cad2cd0f71c669b364d7eb682d71c143491 Mon Sep 17 00:00:00 2001
From: Mohamed Ali <[email protected]>
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)



  [application/octet-stream] 0001-Always-validate-parent-index-in-ALTER-INDEX-ATTACH.patch (3.0K, 3-0001-Always-validate-parent-index-in-ALTER-INDEX-ATTACH.patch)
  download | inline diff:
From af4c2b7b438585240f134aa50ec92c0b838e4573 Mon Sep 17 00:00:00 2001
From: Mohamed Ali <[email protected]>
Date: Fri, 27 Mar 2026 19:56:28 -0700
Subject: [PATCH] fix: Always validate parent index in ALTER INDEX ATTACH
 PARTITION

When ALTER INDEX ... ATTACH PARTITION is executed on a partition index
that is already attached to the parent, the entire if-block is skipped,
including the call to validatePartitionedIndex(). This means that if a
previously invalid partition index has been repaired via REINDEX, there
is no way to trigger re-validation of the parent partitioned index.

Move the validatePartitionedIndex() call outside the if-block so it
runs unconditionally. This allows users to re-run ALTER INDEX ATTACH
PARTITION on an already-attached index to trigger parent validation
after a child has been fixed.

When the partition is already attached, emit a NOTICE informing the
user that the index is already attached and that only parent validation
is being performed. This follows the existing PostgreSQL convention of
using NOTICE for informational messages about no-op or reduced-scope
operations (e.g., DROP IF EXISTS, CREATE INDEX IF NOT EXISTS).

NOTICE:  partition index "child_idx" is already attached to "parent_idx", validating parent index

validatePartitionedIndex() is idempotent and cheap (it scans
pg_inherits and counts valid children), so calling it when not strictly
needed has negligible performance impact.

---
 src/backend/commands/tablecmds.c | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c69c12d..0b29180 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -21917,7 +21917,7 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partIdx));
 
-	/* Silently do nothing if already in the right state */
+	/* Check if already attached to this parent */
 	currParent = partIdx->rd_rel->relispartition ?
 		get_partition_parent(partIdxId, false) : InvalidOid;
 	if (currParent != RelationGetRelid(parentIdx))
@@ -22023,9 +22023,22 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 										  RelationGetRelid(partTbl));
 
 		free_attrmap(attmap);
-
-		validatePartitionedIndex(parentIdx, parentTbl);
 	}
+	else
+	{
+		ereport(NOTICE,
+				(errmsg("partition index \"%s\" is already attached to \"%s\", validating parent index",
+						RelationGetRelationName(partIdx),
+						RelationGetRelationName(parentIdx))));
+	}
+
+	/*
+	 * Always validate the parent partitioned index, even if the partition
+	 * was already attached.  This handles the case where a previously
+	 * invalid partition index has been repaired (e.g., via REINDEX) and
+	 * the parent can now be marked valid.
+	 */
+	validatePartitionedIndex(parentIdx, parentTbl);
 
 	relation_close(parentTbl, AccessShareLock);
 	/* keep these locks till commit */
-- 
2.50.1 (Apple Git-155)



^ permalink  raw  reply  [nested|flat] 12+ messages in thread

* Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired
  2026-04-09 06:16 [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Mohamed ALi <[email protected]>
@ 2026-04-11 00:55 ` Haibo Yan <[email protected]>
  2026-04-11 02:18   ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Haibo Yan <[email protected]>
  0 siblings, 1 reply; 12+ messages in thread

From: Haibo Yan @ 2026-04-11 00:55 UTC (permalink / raw)
  To: Mohamed ALi <[email protected]>; +Cc: [email protected]

On Wed, Apr 8, 2026 at 11:17 PM Mohamed ALi <[email protected]> wrote:

> Hi hackers,
>
> A partitioned (parent) index in PostgreSQL can become permanently
> stuck with `indisvalid = false` even after all of its child partition
> indexes have been repaired and are valid. There is no built-in
> mechanism to re-validate the parent index after a child is fixed via
> `REINDEX`. This affects all currently supported PostgreSQL versions
> (13 through 18)
> The root cause is that `validatePartitionedIndex()` — the only
> function that can mark a partitioned index as valid is never called
> after `REINDEX` operations, and is skipped when re-running `ALTER
> INDEX ATTACH PARTITION` on an already-attached index.
>
> How the Bug Manifests
>
> Typical Scenario :
> 1. A partitioned table has multiple partitions.
> 2. The user creates indexes on partitions concurrently. One fails (due
> to deadlock, cancellation, timeout, etc.), leaving an invalid
> partition index.
> 3. A parent index is created (or the invalid index is attached to an
> existing parent). The parent is correctly marked `indisvalid = false`
> because at least one child is invalid.
> 4. The user fixes the broken child index with `REINDEX INDEX CONCURRENTLY`.
> 5. The child index becomes valid (`indisvalid = true`).
> 6. The parent index remains `indisvalid = false` permanently. No SQL
> command can fix it.
>
> Reproduction steps:
>
> ```sql
> -- ============================================================
> -- SETUP: Partitioned table with two partitions and sample data
> -- ============================================================
> DROP TABLE IF EXISTS orders;
> CREATE TABLE orders (
>     id serial,
>     order_date date NOT NULL,
>     amount numeric
> ) PARTITION BY RANGE (order_date);
> CREATE TABLE orders_2023 PARTITION OF orders
>     FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');
> CREATE TABLE orders_2024 PARTITION OF orders
>     FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
> INSERT INTO orders (order_date, amount)
> SELECT d, random() * 1000
> FROM generate_series('2023-01-01'::date, '2023-12-31'::date, '1 day') d;
> INSERT INTO orders (order_date, amount)
> SELECT d, random() * 1000
> FROM generate_series('2024-01-01'::date, '2024-12-31'::date, '1 day') d;
> -- ============================================================
> -- STEP 1: Create parent index with ONLY (starts as invalid)
> -- ============================================================
> CREATE INDEX orders_amount_idx ON ONLY orders (amount);
> -- Verify: parent index is invalid (no children attached yet)
> SELECT c.relname, i.indisvalid
> FROM pg_class c
> JOIN pg_index i ON c.oid = i.indexrelid
> WHERE c.relname LIKE 'orders%idx%'
> ORDER BY c.relname;
> -- Expected:
> --  orders_amount_idx | f
> -- ============================================================
> -- STEP 2: Create valid index on first partition
> -- ============================================================
> CREATE INDEX CONCURRENTLY orders_2023_amount_idx ON orders_2023 (amount);
> -- ============================================================
> -- STEP 3: Create an INVALID index on second partition
> -- ============================================================
> -- In a separate session, hold a lock:
> BEGIN; LOCK TABLE orders_2024 IN SHARE MODE;
> -- Then in the main session:
> SET statement_timeout = '1ms';
> CREATE INDEX CONCURRENTLY orders_2024_amount_idx ON orders_2024 (amount);
> RESET statement_timeout;
> -- it will fail/timeout, leaving an invalid index.
> -- Verify state:
> SELECT c.relname, i.indisvalid
> FROM pg_class c
> JOIN pg_index i ON c.oid = i.indexrelid
> WHERE c.relname LIKE 'orders%idx%'
> ORDER BY c.relname;
> -- Expected:
> --  orders_2023_amount_idx | t   (valid)
> --  orders_2024_amount_idx | f   (invalid)
> --  orders_amount_idx      | f   (invalid, created with ONLY)
> -- ============================================================
> -- STEP 4: Attach both partition indexes to the parent
> -- ============================================================
> -- Attach the invalid one first
> ALTER INDEX orders_amount_idx ATTACH PARTITION orders_2024_amount_idx;
> -- Succeeds. Parent stays invalid (correct — child is invalid).
> -- Attach the valid one
> ALTER INDEX orders_amount_idx ATTACH PARTITION orders_2023_amount_idx;
> -- Succeeds. Parent still invalid (correct — one child still invalid).
> -- Verify attachment and validity:
> SELECT c.relname, i.indisvalid,
>        pg_get_indexdef(i.indexrelid) AS indexdef
> FROM pg_class c
> JOIN pg_index i ON c.oid = i.indexrelid
> WHERE c.relname LIKE 'orders%amount%'
> ORDER BY c.relname;
> -- Expected:
> --  orders_2023_amount_idx | t
> --  orders_2024_amount_idx | f
> --  orders_amount_idx      | f
> -- ============================================================
> -- STEP 5: Fix the invalid child index via REINDEX
> -- ============================================================
> REINDEX INDEX CONCURRENTLY orders_2024_amount_idx;
> -- Verify: child is now valid
> SELECT c.relname, i.indisvalid
> FROM pg_class c
> JOIN pg_index i ON c.oid = i.indexrelid
> WHERE c.relname LIKE 'orders%amount%'
> ORDER BY c.relname;
> -- ACTUAL (buggy) result:
> --  orders_2023_amount_idx | t   (valid)
> --  orders_2024_amount_idx | t   (valid — fixed by REINDEX)
> --  orders_amount_idx      | f   (STILL INVALID — this is the bug!)
> --
> -- EXPECTED result (if bug were fixed):
> --  orders_2023_amount_idx | t
> --  orders_2024_amount_idx | t
> --  orders_amount_idx      | t   (should be valid now)
> -- ============================================================
> -- STEP 6: Demonstrate that re-running ATTACH does not help
> -- ============================================================
> ALTER INDEX orders_amount_idx ATTACH PARTITION orders_2024_amount_idx;
> -- Returns "ALTER INDEX" (succeeds silently, does nothing)
> SELECT c.relname, i.indisvalid
> FROM pg_class c
> JOIN pg_index i ON c.oid = i.indexrelid
> WHERE c.relname LIKE 'orders%amount%'
> ORDER BY c.relname;
> -- Parent is STILL invalid. The "silently do nothing" path
> -- skips validatePartitionedIndex() entirely.
> -- ============================================================
> -- CLEANUP
> -- ============================================================
> DROP TABLE orders;
> ```
>
>
> Root Cause Analysis:
>
> Where `validatePartitionedIndex()` Is Called
>
> The function is called in exactly these code paths:
> 1. During `ALTER INDEX ... ATTACH PARTITION` — inside
> `ATExecAttachPartitionIdx()`
> 2. During `ALTER TABLE ... ATTACH PARTITION` — via
> `AttachPartitionEnsureIndexes()`
> 3. During `CREATE INDEX` on partitioned tables — via `DefineIndex()`
> It is NOT called:
> - After `REINDEX` of a partitioned index
> - During any maintenance operation
> - As any periodic validation check
>
> Bug 1: REINDEX Does Not Validate Parent
>
>
> When `reindex_index()` in `src/backend/catalog/index.c` marks a
> partition index as valid (setting `indisvalid = true`), it does not
> check whether the parent partitioned index should also become valid.
> The function simply updates the child's `pg_index` entry and returns.
>
> Bug 2: Re-running ATTACH Skips Validation
>
>
> In `ATExecAttachPartitionIdx()` (tablecmds.c, around line 21923 in PG
> 16 / line ~22900 in HEAD):
>
> https://github.com/postgres/postgres/blob/master/src/backend/commands/tablecmds.c#L21923
>
> ```c
> /* Silently do nothing if already in the right state */
> currParent = partIdx->rd_rel->relispartition ?
>     get_partition_parent(partIdxId, false) : InvalidOid;
> if (currParent != RelationGetRelid(parentIdx))
> {
>     // ... all validation checks and attachment logic ...
>     validatePartitionedIndex(parentIdx, parentTbl);  // ONLY called here
> }
> // If already attached, entire block is skipped — no validation!
> ```
>
> When the child is already attached (`currParent == parentIdx`), the
> condition is false, the entire if-block is skipped, and
> `validatePartitionedIndex()` is never called. The comment "Silently do
> nothing if already in the right state" is misleading  "already
> attached" does not mean "parent validity is correct."
>
> Proposed Fixes:
>
> Fix 1 : Always Validate Parent Index in ALTER INDEX ATTACH PARTITION
>
> Patch File : 0001-Always-validate-parent-index-in-ALTER-INDEX-ATTACH.patch
>
> Move the validatePartitionedIndex() call outside the if-block so it runs
> unconditionally — both when a new attachment is made and when the
> partition is
> already attached. This provides a user-accessible recovery path: after
> fixing a
> child index with REINDEX, re-running ALTER INDEX ATTACH PARTITION triggers
> parent validation.
>
> When the partition is already attached, a NOTICE is emitted:
>
> NOTICE:  partition index "child_idx" is already attached to
> "parent_idx", validating parent index
>
>
> This follows PostgreSQL's existing convention of using NOTICE for
> informational messages about no-op or reduced-scope operations (e.g.,
> DROP TABLE IF EXISTS, CREATE INDEX IF NOT EXISTS). It tells the user:
>
> 1- Nothing went wrong
> 2- The index was already attached (so they know the state)
> 3-  Validation still happened (so they know the fix path works)
>
>
> Fix 2: Validate Parent Partitioned Index After REINDEX of Child
>
> Patch File : 0001-Validate-parent-partitioned-index-after-REINDEX.patch
>
> Same underlying bug but this patch addresses it from the
> REINDEX side. When a partition index is repaired via REINDEX or
> REINDEX CONCURRENTLY, the parent partitioned index remains permanently
> stuck with indisvalid = false even though all children are now valid.
>
> This is because validatePartitionedIndex() — the only function that can
> mark a partitioned index as valid is never called from any REINDEX code
> path.
>
>
> validatePartitionedIndex() is only called during:
>
> 1- ALTER INDEX ... ATTACH PARTITION (tablecmds.c)
> 2- ALTER TABLE ... ATTACH PARTITION (tablecmds.c)
> 3- CREATE INDEX on partitioned tables (indexcmds.c)
>
> It is NOT called after:
>
> 1- REINDEX INDEX (regular) — handled by reindex_index() in index.c
> 2- REINDEX INDEX CONCURRENTLY — handled by ReindexRelationConcurrently()
>
> in indexcmds.c, which uses index_concurrently_swap() in index.c
>
> Three changes are made:
>
> 1. Make validatePartitionedIndex() public
> The function was static in tablecmds.c. It is now exported via
> tablecmds.h so it can be called from index.c and indexcmds.c.
>
> Files changed:
>
> src/backend/commands/tablecmds.c — remove static, update comment
> src/include/commands/tablecmds.h — add extern declaration
>
> 2. Call from reindex_index() (regular REINDEX)
> After reindex_index() marks a partition index as valid (indisvalid = true),
> check if the index is a partition (iRel->rd_rel->relispartition) and if so,
> look up the parent and call validatePartitionedIndex().
>
> A CommandCounterIncrement() is required before the call so that the child's
> updated indisvalid is visible to the syscache lookup that
> validatePartitionedIndex() performs internally.
>
> File changed: src/backend/catalog/index.c
>
> 3. Call from ReindexRelationConcurrently() (REINDEX CONCURRENTLY)
> REINDEX CONCURRENTLY uses a completely different code path: it creates a
> new
> index, builds it concurrently, then swaps it with the old one via
> index_concurrently_swap(). The new index inherits the old index's partition
> status during the swap.
>
> After the swap and the existing CommandCounterIncrement() (which makes the
> swap visible), check if the new index is a partition and call
> validatePartitionedIndex() on the parent.
>
> File changed: src/backend/commands/indexcmds.c
>
> Multi-level Hierarchy Support
> validatePartitionedIndex() already handles multi-level partition
> hierarchies.
> When it marks a mid-level parent valid, it checks if that parent is itself
> a
> partition and recursively validates the grandparent. No additional
> recursion
> logic is needed in the REINDEX patches.
>
>
> Thanks,
> Mohamed Ali
> Senior DBE
> AWS RDS
>

Hi, Mohamed

Thanks for working on this. I went through the problem statement and the
two proposed fixes. I agree that the underlying issue looks real: after
repairing an invalid child index with REINDEX, the parent partitioned index
can remain stuck in an invalid state because validatePartitionedIndex() is
not reached from the relevant REINDEX paths. The analysis around
ATExecAttachPartitionIdx() also looks correct: when the child index is
already attached, the current code takes the no-op path and skips the
validation call entirely.

Overall, I think this is worth fixing, but I do not view the two patches
equally.

I think patch 2 is the real fix. The state transition that matters here is
that a child index goes from invalid to valid, and the natural place to
trigger parent revalidation is where that transition actually happens,
namely in REINDEX. By contrast, patch 1 feels more like a secondary
hardening/workaround path: it makes ALTER INDEX ... ATTACH PARTITION retry
parent validation even in the already-attached case, but that is not really
where the underlying state change happens.

My main comments are below.

1. Patch 2 should be treated as the primary fix

This seems like the correct place to repair the catalog state. If REINDEX
repairs a partition index that was previously invalid, and that index is
attached to a partitioned parent index, then rechecking the parent with
validatePartitionedIndex() is a reasonable and direct solution.

I also think it is good that the patch covers both the regular REINDEX path
and the REINDEX CONCURRENTLY path. Those are distinct enough that both need
explicit attention, and the extra CommandCounterIncrement() before
revalidation also seems necessary.

So at a high level, this patch makes sense to me.

2. I am less convinced by patch 1 in its current form

The main issue here is not correctness, but design and placement.

Once the child is already attached, ALTER INDEX ... ATTACH PARTITION is
conceptually supposed to be a no-op. With this patch, it becomes a generic
“retry parent validation” hook. That means users can run an attach command
that does not change attachment state at all, yet still triggers a full
parent validation attempt.

That is especially questionable if there are still other invalid child
indexes elsewhere under the same parent. In that case, each
already-attached ATTACH command will do the validation work again, but it
is already known in advance that the parent still cannot become valid. So
this is not “reinvalidating” anything, but it is repeated checking with no
state change, which feels misplaced on a no-op path.

Because of that, I do not think patch 1 should be the main bug fix. At
most, I would see it as an optional hardening patch.

3. The NOTICE added by patch 1 does not seem like a good fit

The existing code explicitly says “Silently do nothing if already in the
right state”. Changing that into a NOTICE every time we hit the
already-attached case seems noisy to me.

If the community decides that the validation call should stay in this path,
I would still suggest dropping the NOTICE. The command is syntactically an
ATTACH command, not a repair command, and emitting a message for an
otherwise no-op case does not feel very PostgreSQL-like.

4. Please double-check coverage of all REINDEX entry points

I agree with the direction in patch 2, but I think reviewers will want
confidence that all relevant REINDEX flows are covered consistently.

For example, it would be good to confirm that the fix behaves correctly not
just for REINDEX INDEX, but also for the broader forms that eventually
reach the same logic, such as REINDEX TABLE, and that there is no alternate
path where a repaired child index can still leave the parent stale.

5. Multi-level partition trees need explicit testing

One especially important point here is recursion. If a repaired child index
causes its immediate parent partitioned index to become valid, and that
parent is itself a child of another partitioned index, we need to be sure
that validity propagates all the way up as intended.

The current reasoning suggests that validatePartitionedIndex() already
handles that, but this is important enough that it should be demonstrated
with a regression test, not just assumed.

Minor comments:


   -

   In patch 1, I would avoid turning the already-attached path into a
   behavioral special case unless there is a strong reason to keep it. It
   would be cleaner if the fix relied primarily on the actual state-changing
   paths.
   -

   If patch 1 remains, I would remove the NOTICE.
   -

   The commit message for patch 2 should clearly explain why REINDEX is the
   right place to do this, rather than making ATTACH PARTITION the repair
   mechanism.
   -

   It may also help to mention explicitly whether the extra validation call
   is only intended for indexes that were previously invalid and have just
   been repaired, since that is an important part of why the patch is
   reasonably narrow.

I do not think the current patches are complete without regression
coverage. At minimum, I think the following tests should be added:


   1.

   Regular REINDEX case

   Create a partitioned table with a parent index left invalid initially,
   then attach child indexes such that one child index is invalid. Repair that
   child with plain REINDEX INDEX, and verify that the child becomes valid
   and the parent also becomes valid.
   2.

   REINDEX CONCURRENTLY case

   Same setup, but use REINDEX INDEX CONCURRENTLY. This should be tested
   separately because the code path is different.
   3.

   Multi-level partition hierarchy

   Use at least a three-level hierarchy and verify that repairing a
   leaf-level invalid child index can cause validity to propagate upward
   through intermediate partitioned indexes.
   4.

   Negative case

   Repair one child index while another child index under the same parent
   remains invalid, and verify that the parent remains invalid.
   5.

   Already-attached ATTACH PARTITION case

   Only if patch 1 is kept: exercise ALTER INDEX ... ATTACH PARTITION on a
   child index that is already attached, and verify both behaviors:


   -

   parent becomes valid if all children are now valid
   -

   parent stays invalid if some other child is still invalid

My overall view is:


   - The bug itself looks real.
   - The diagnosis looks sound.
   - Patch 2 is the right direction and should be the primary fix.
   - Patch 1 is much less compelling as written, and I would not take it as
   the main solution.
   - The series needs regression tests before it is ready.

Regards,
Haibo


^ permalink  raw  reply  [nested|flat] 12+ messages in thread

* Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired
  2026-04-09 06:16 [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Mohamed ALi <[email protected]>
  2026-04-11 00:55 ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Haibo Yan <[email protected]>
@ 2026-04-11 02:18   ` Haibo Yan <[email protected]>
  2026-04-11 16:10     ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Sami Imseih <[email protected]>
  0 siblings, 1 reply; 12+ messages in thread

From: Haibo Yan @ 2026-04-11 02:18 UTC (permalink / raw)
  To: Mohamed ALi <[email protected]>; +Cc: [email protected]

On Fri, Apr 10, 2026 at 5:55 PM Haibo Yan <[email protected]> wrote:

> On Wed, Apr 8, 2026 at 11:17 PM Mohamed ALi <[email protected]> wrote:
>
>> Hi hackers,
>>
>> A partitioned (parent) index in PostgreSQL can become permanently
>> stuck with `indisvalid = false` even after all of its child partition
>> indexes have been repaired and are valid. There is no built-in
>> mechanism to re-validate the parent index after a child is fixed via
>> `REINDEX`. This affects all currently supported PostgreSQL versions
>> (13 through 18)
>> The root cause is that `validatePartitionedIndex()` — the only
>> function that can mark a partitioned index as valid is never called
>> after `REINDEX` operations, and is skipped when re-running `ALTER
>> INDEX ATTACH PARTITION` on an already-attached index.
>>
>> How the Bug Manifests
>>
>> Typical Scenario :
>> 1. A partitioned table has multiple partitions.
>> 2. The user creates indexes on partitions concurrently. One fails (due
>> to deadlock, cancellation, timeout, etc.), leaving an invalid
>> partition index.
>> 3. A parent index is created (or the invalid index is attached to an
>> existing parent). The parent is correctly marked `indisvalid = false`
>> because at least one child is invalid.
>> 4. The user fixes the broken child index with `REINDEX INDEX
>> CONCURRENTLY`.
>> 5. The child index becomes valid (`indisvalid = true`).
>> 6. The parent index remains `indisvalid = false` permanently. No SQL
>> command can fix it.
>>
>> Reproduction steps:
>>
>> ```sql
>> -- ============================================================
>> -- SETUP: Partitioned table with two partitions and sample data
>> -- ============================================================
>> DROP TABLE IF EXISTS orders;
>> CREATE TABLE orders (
>>     id serial,
>>     order_date date NOT NULL,
>>     amount numeric
>> ) PARTITION BY RANGE (order_date);
>> CREATE TABLE orders_2023 PARTITION OF orders
>>     FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');
>> CREATE TABLE orders_2024 PARTITION OF orders
>>     FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
>> INSERT INTO orders (order_date, amount)
>> SELECT d, random() * 1000
>> FROM generate_series('2023-01-01'::date, '2023-12-31'::date, '1 day') d;
>> INSERT INTO orders (order_date, amount)
>> SELECT d, random() * 1000
>> FROM generate_series('2024-01-01'::date, '2024-12-31'::date, '1 day') d;
>> -- ============================================================
>> -- STEP 1: Create parent index with ONLY (starts as invalid)
>> -- ============================================================
>> CREATE INDEX orders_amount_idx ON ONLY orders (amount);
>> -- Verify: parent index is invalid (no children attached yet)
>> SELECT c.relname, i.indisvalid
>> FROM pg_class c
>> JOIN pg_index i ON c.oid = i.indexrelid
>> WHERE c.relname LIKE 'orders%idx%'
>> ORDER BY c.relname;
>> -- Expected:
>> --  orders_amount_idx | f
>> -- ============================================================
>> -- STEP 2: Create valid index on first partition
>> -- ============================================================
>> CREATE INDEX CONCURRENTLY orders_2023_amount_idx ON orders_2023 (amount);
>> -- ============================================================
>> -- STEP 3: Create an INVALID index on second partition
>> -- ============================================================
>> -- In a separate session, hold a lock:
>> BEGIN; LOCK TABLE orders_2024 IN SHARE MODE;
>> -- Then in the main session:
>> SET statement_timeout = '1ms';
>> CREATE INDEX CONCURRENTLY orders_2024_amount_idx ON orders_2024 (amount);
>> RESET statement_timeout;
>> -- it will fail/timeout, leaving an invalid index.
>> -- Verify state:
>> SELECT c.relname, i.indisvalid
>> FROM pg_class c
>> JOIN pg_index i ON c.oid = i.indexrelid
>> WHERE c.relname LIKE 'orders%idx%'
>> ORDER BY c.relname;
>> -- Expected:
>> --  orders_2023_amount_idx | t   (valid)
>> --  orders_2024_amount_idx | f   (invalid)
>> --  orders_amount_idx      | f   (invalid, created with ONLY)
>> -- ============================================================
>> -- STEP 4: Attach both partition indexes to the parent
>> -- ============================================================
>> -- Attach the invalid one first
>> ALTER INDEX orders_amount_idx ATTACH PARTITION orders_2024_amount_idx;
>> -- Succeeds. Parent stays invalid (correct — child is invalid).
>> -- Attach the valid one
>> ALTER INDEX orders_amount_idx ATTACH PARTITION orders_2023_amount_idx;
>> -- Succeeds. Parent still invalid (correct — one child still invalid).
>> -- Verify attachment and validity:
>> SELECT c.relname, i.indisvalid,
>>        pg_get_indexdef(i.indexrelid) AS indexdef
>> FROM pg_class c
>> JOIN pg_index i ON c.oid = i.indexrelid
>> WHERE c.relname LIKE 'orders%amount%'
>> ORDER BY c.relname;
>> -- Expected:
>> --  orders_2023_amount_idx | t
>> --  orders_2024_amount_idx | f
>> --  orders_amount_idx      | f
>> -- ============================================================
>> -- STEP 5: Fix the invalid child index via REINDEX
>> -- ============================================================
>> REINDEX INDEX CONCURRENTLY orders_2024_amount_idx;
>> -- Verify: child is now valid
>> SELECT c.relname, i.indisvalid
>> FROM pg_class c
>> JOIN pg_index i ON c.oid = i.indexrelid
>> WHERE c.relname LIKE 'orders%amount%'
>> ORDER BY c.relname;
>> -- ACTUAL (buggy) result:
>> --  orders_2023_amount_idx | t   (valid)
>> --  orders_2024_amount_idx | t   (valid — fixed by REINDEX)
>> --  orders_amount_idx      | f   (STILL INVALID — this is the bug!)
>> --
>> -- EXPECTED result (if bug were fixed):
>> --  orders_2023_amount_idx | t
>> --  orders_2024_amount_idx | t
>> --  orders_amount_idx      | t   (should be valid now)
>> -- ============================================================
>> -- STEP 6: Demonstrate that re-running ATTACH does not help
>> -- ============================================================
>> ALTER INDEX orders_amount_idx ATTACH PARTITION orders_2024_amount_idx;
>> -- Returns "ALTER INDEX" (succeeds silently, does nothing)
>> SELECT c.relname, i.indisvalid
>> FROM pg_class c
>> JOIN pg_index i ON c.oid = i.indexrelid
>> WHERE c.relname LIKE 'orders%amount%'
>> ORDER BY c.relname;
>> -- Parent is STILL invalid. The "silently do nothing" path
>> -- skips validatePartitionedIndex() entirely.
>> -- ============================================================
>> -- CLEANUP
>> -- ============================================================
>> DROP TABLE orders;
>> ```
>>
>>
>> Root Cause Analysis:
>>
>> Where `validatePartitionedIndex()` Is Called
>>
>> The function is called in exactly these code paths:
>> 1. During `ALTER INDEX ... ATTACH PARTITION` — inside
>> `ATExecAttachPartitionIdx()`
>> 2. During `ALTER TABLE ... ATTACH PARTITION` — via
>> `AttachPartitionEnsureIndexes()`
>> 3. During `CREATE INDEX` on partitioned tables — via `DefineIndex()`
>> It is NOT called:
>> - After `REINDEX` of a partitioned index
>> - During any maintenance operation
>> - As any periodic validation check
>>
>> Bug 1: REINDEX Does Not Validate Parent
>>
>>
>> When `reindex_index()` in `src/backend/catalog/index.c` marks a
>> partition index as valid (setting `indisvalid = true`), it does not
>> check whether the parent partitioned index should also become valid.
>> The function simply updates the child's `pg_index` entry and returns.
>>
>> Bug 2: Re-running ATTACH Skips Validation
>>
>>
>> In `ATExecAttachPartitionIdx()` (tablecmds.c, around line 21923 in PG
>> 16 / line ~22900 in HEAD):
>>
>> https://github.com/postgres/postgres/blob/master/src/backend/commands/tablecmds.c#L21923
>>
>> ```c
>> /* Silently do nothing if already in the right state */
>> currParent = partIdx->rd_rel->relispartition ?
>>     get_partition_parent(partIdxId, false) : InvalidOid;
>> if (currParent != RelationGetRelid(parentIdx))
>> {
>>     // ... all validation checks and attachment logic ...
>>     validatePartitionedIndex(parentIdx, parentTbl);  // ONLY called here
>> }
>> // If already attached, entire block is skipped — no validation!
>> ```
>>
>> When the child is already attached (`currParent == parentIdx`), the
>> condition is false, the entire if-block is skipped, and
>> `validatePartitionedIndex()` is never called. The comment "Silently do
>> nothing if already in the right state" is misleading  "already
>> attached" does not mean "parent validity is correct."
>>
>> Proposed Fixes:
>>
>> Fix 1 : Always Validate Parent Index in ALTER INDEX ATTACH PARTITION
>>
>> Patch File : 0001-Always-validate-parent-index-in-ALTER-INDEX-ATTACH.patch
>>
>> Move the validatePartitionedIndex() call outside the if-block so it runs
>> unconditionally — both when a new attachment is made and when the
>> partition is
>> already attached. This provides a user-accessible recovery path: after
>> fixing a
>> child index with REINDEX, re-running ALTER INDEX ATTACH PARTITION triggers
>> parent validation.
>>
>> When the partition is already attached, a NOTICE is emitted:
>>
>> NOTICE:  partition index "child_idx" is already attached to
>> "parent_idx", validating parent index
>>
>>
>> This follows PostgreSQL's existing convention of using NOTICE for
>> informational messages about no-op or reduced-scope operations (e.g.,
>> DROP TABLE IF EXISTS, CREATE INDEX IF NOT EXISTS). It tells the user:
>>
>> 1- Nothing went wrong
>> 2- The index was already attached (so they know the state)
>> 3-  Validation still happened (so they know the fix path works)
>>
>>
>> Fix 2: Validate Parent Partitioned Index After REINDEX of Child
>>
>> Patch File : 0001-Validate-parent-partitioned-index-after-REINDEX.patch
>>
>> Same underlying bug but this patch addresses it from the
>> REINDEX side. When a partition index is repaired via REINDEX or
>> REINDEX CONCURRENTLY, the parent partitioned index remains permanently
>> stuck with indisvalid = false even though all children are now valid.
>>
>> This is because validatePartitionedIndex() — the only function that can
>> mark a partitioned index as valid is never called from any REINDEX code
>> path.
>>
>>
>> validatePartitionedIndex() is only called during:
>>
>> 1- ALTER INDEX ... ATTACH PARTITION (tablecmds.c)
>> 2- ALTER TABLE ... ATTACH PARTITION (tablecmds.c)
>> 3- CREATE INDEX on partitioned tables (indexcmds.c)
>>
>> It is NOT called after:
>>
>> 1- REINDEX INDEX (regular) — handled by reindex_index() in index.c
>> 2- REINDEX INDEX CONCURRENTLY — handled by ReindexRelationConcurrently()
>>
>> in indexcmds.c, which uses index_concurrently_swap() in index.c
>>
>> Three changes are made:
>>
>> 1. Make validatePartitionedIndex() public
>> The function was static in tablecmds.c. It is now exported via
>> tablecmds.h so it can be called from index.c and indexcmds.c.
>>
>> Files changed:
>>
>> src/backend/commands/tablecmds.c — remove static, update comment
>> src/include/commands/tablecmds.h — add extern declaration
>>
>> 2. Call from reindex_index() (regular REINDEX)
>> After reindex_index() marks a partition index as valid (indisvalid =
>> true),
>> check if the index is a partition (iRel->rd_rel->relispartition) and if
>> so,
>> look up the parent and call validatePartitionedIndex().
>>
>> A CommandCounterIncrement() is required before the call so that the
>> child's
>> updated indisvalid is visible to the syscache lookup that
>> validatePartitionedIndex() performs internally.
>>
>> File changed: src/backend/catalog/index.c
>>
>> 3. Call from ReindexRelationConcurrently() (REINDEX CONCURRENTLY)
>> REINDEX CONCURRENTLY uses a completely different code path: it creates a
>> new
>> index, builds it concurrently, then swaps it with the old one via
>> index_concurrently_swap(). The new index inherits the old index's
>> partition
>> status during the swap.
>>
>> After the swap and the existing CommandCounterIncrement() (which makes the
>> swap visible), check if the new index is a partition and call
>> validatePartitionedIndex() on the parent.
>>
>> File changed: src/backend/commands/indexcmds.c
>>
>> Multi-level Hierarchy Support
>> validatePartitionedIndex() already handles multi-level partition
>> hierarchies.
>> When it marks a mid-level parent valid, it checks if that parent is
>> itself a
>> partition and recursively validates the grandparent. No additional
>> recursion
>> logic is needed in the REINDEX patches.
>>
>>
>> Thanks,
>> Mohamed Ali
>> Senior DBE
>> AWS RDS
>>
>
> Hi, Mohamed
>
> Thanks for working on this. I went through the problem statement and the
> two proposed fixes. I agree that the underlying issue looks real: after
> repairing an invalid child index with REINDEX, the parent partitioned index
> can remain stuck in an invalid state because validatePartitionedIndex()
> is not reached from the relevant REINDEX paths. The analysis around
> ATExecAttachPartitionIdx() also looks correct: when the child index is
> already attached, the current code takes the no-op path and skips the
> validation call entirely.
>
> Overall, I think this is worth fixing, but I do not view the two patches
> equally.
>
> I think patch 2 is the real fix. The state transition that matters here is
> that a child index goes from invalid to valid, and the natural place to
> trigger parent revalidation is where that transition actually happens,
> namely in REINDEX. By contrast, patch 1 feels more like a secondary
> hardening/workaround path: it makes ALTER INDEX ... ATTACH PARTITION
> retry parent validation even in the already-attached case, but that is not
> really where the underlying state change happens.
>
> My main comments are below.
>
> 1. Patch 2 should be treated as the primary fix
>
> This seems like the correct place to repair the catalog state. If REINDEX
> repairs a partition index that was previously invalid, and that index is
> attached to a partitioned parent index, then rechecking the parent with
> validatePartitionedIndex() is a reasonable and direct solution.
>
> I also think it is good that the patch covers both the regular REINDEX
> path and the REINDEX CONCURRENTLY path. Those are distinct enough that
> both need explicit attention, and the extra CommandCounterIncrement()
> before revalidation also seems necessary.
>
> So at a high level, this patch makes sense to me.
>
> 2. I am less convinced by patch 1 in its current form
>
> The main issue here is not correctness, but design and placement.
>
> Once the child is already attached, ALTER INDEX ... ATTACH PARTITION is
> conceptually supposed to be a no-op. With this patch, it becomes a generic
> “retry parent validation” hook. That means users can run an attach command
> that does not change attachment state at all, yet still triggers a full
> parent validation attempt.
>
> That is especially questionable if there are still other invalid child
> indexes elsewhere under the same parent. In that case, each
> already-attached ATTACH command will do the validation work again, but it
> is already known in advance that the parent still cannot become valid. So
> this is not “reinvalidating” anything, but it is repeated checking with no
> state change, which feels misplaced on a no-op path.
>
> Because of that, I do not think patch 1 should be the main bug fix. At
> most, I would see it as an optional hardening patch.
>
> 3. The NOTICE added by patch 1 does not seem like a good fit
>
> The existing code explicitly says “Silently do nothing if already in the
> right state”. Changing that into a NOTICE every time we hit the
> already-attached case seems noisy to me.
>
> If the community decides that the validation call should stay in this
> path, I would still suggest dropping the NOTICE. The command is
> syntactically an ATTACH command, not a repair command, and emitting a
> message for an otherwise no-op case does not feel very PostgreSQL-like.
>
> 4. Please double-check coverage of all REINDEX entry points
>
> I agree with the direction in patch 2, but I think reviewers will want
> confidence that all relevant REINDEX flows are covered consistently.
>
> For example, it would be good to confirm that the fix behaves correctly
> not just for REINDEX INDEX, but also for the broader forms that
> eventually reach the same logic, such as REINDEX TABLE, and that there is
> no alternate path where a repaired child index can still leave the parent
> stale.
>
> 5. Multi-level partition trees need explicit testing
>
> One especially important point here is recursion. If a repaired child
> index causes its immediate parent partitioned index to become valid, and
> that parent is itself a child of another partitioned index, we need to be
> sure that validity propagates all the way up as intended.
>
> The current reasoning suggests that validatePartitionedIndex() already
> handles that, but this is important enough that it should be demonstrated
> with a regression test, not just assumed.
>
> Minor comments:
>
>
>    -
>
>    In patch 1, I would avoid turning the already-attached path into a
>    behavioral special case unless there is a strong reason to keep it. It
>    would be cleaner if the fix relied primarily on the actual state-changing
>    paths.
>    -
>
>    If patch 1 remains, I would remove the NOTICE.
>    -
>
>    The commit message for patch 2 should clearly explain why REINDEX is
>    the right place to do this, rather than making ATTACH PARTITION the repair
>    mechanism.
>    -
>
>    It may also help to mention explicitly whether the extra validation
>    call is only intended for indexes that were previously invalid and have
>    just been repaired, since that is an important part of why the patch is
>    reasonably narrow.
>
> I do not think the current patches are complete without regression
> coverage. At minimum, I think the following tests should be added:
>
>
>    1.
>
>    Regular REINDEX case
>
>    Create a partitioned table with a parent index left invalid initially,
>    then attach child indexes such that one child index is invalid. Repair that
>    child with plain REINDEX INDEX, and verify that the child becomes
>    valid and the parent also becomes valid.
>    2.
>
>    REINDEX CONCURRENTLY case
>
>    Same setup, but use REINDEX INDEX CONCURRENTLY. This should be tested
>    separately because the code path is different.
>    3.
>
>    Multi-level partition hierarchy
>
>    Use at least a three-level hierarchy and verify that repairing a
>    leaf-level invalid child index can cause validity to propagate upward
>    through intermediate partitioned indexes.
>    4.
>
>    Negative case
>
>    Repair one child index while another child index under the same parent
>    remains invalid, and verify that the parent remains invalid.
>    5.
>
>    Already-attached ATTACH PARTITION case
>
>    Only if patch 1 is kept: exercise ALTER INDEX ... ATTACH PARTITION on
>    a child index that is already attached, and verify both behaviors:
>
>
>    -
>
>    parent becomes valid if all children are now valid
>    -
>
>    parent stays invalid if some other child is still invalid
>
> My overall view is:
>
>
>    - The bug itself looks real.
>    - The diagnosis looks sound.
>    - Patch 2 is the right direction and should be the primary fix.
>    - Patch 1 is much less compelling as written, and I would not take it
>    as the main solution.
>    - The series needs regression tests before it is ready.
>
> Regards,
> Haibo
>

Hi, Mohamed
I took a look at this patch and added some regression coverage for it.
While doing that, I found that the current concurrent-path fix is not quite
complete. The new tests exposed crashes in existing REINDEX CONCURRENTLY
regression coverage, and the root cause appears to be that in the
concurrent path, validatePartitionedIndex() can eventually reach catalog
update code at a point where there is no active/registered snapshot. That
leads to the HaveRegisteredOrActiveSnapshot() assertion failure.
So the overall bug diagnosis still looks correct to me, and the plain
REINDEX direction also looks right, but the REINDEX CONCURRENTLY path needs
an additional fix around the call site/context.
Based on that, I prepared a v2 on top of your patch. It includes:

   - a fix for the concurrent-path snapshot issue
   - narrower handling for the concurrent path so it only does parent
   revalidation for the actual repair case
   - regression tests covering:
   - plain REINDEX INDEX
   - a negative case where another invalid sibling keeps the parent invalid
   - multi-level partition hierarchies
   - REINDEX TABLE

If my understanding above looks right to you, I think this v2 is a better
base for further review.
Thanks,
Haibo


Attachments:

  [application/octet-stream] v2-0001-fix-revalidate-parent-partitioned-index-after-chi.patch (22.8K, 3-v2-0001-fix-revalidate-parent-partitioned-index-after-chi.patch)
  download | inline diff:
From cbcf27dd51f08d9d3103dd98d8f9f1ec2a98ee11 Mon Sep 17 00:00:00 2001
From: Haibo Yan <[email protected]>
Date: Fri, 10 Apr 2026 19:07:17 -0700
Subject: [PATCH v2] fix: revalidate parent partitioned index after child
 REINDEX

When REINDEX repairs an invalid child partition index, the parent
partitioned index can remain stuck with indisvalid = false because
validatePartitionedIndex() is not called from the corresponding
REINDEX path.

Export validatePartitionedIndex() and invoke it when REINDEX actually
repairs a previously invalid child partition index.  For plain
REINDEX, do this after CommandCounterIncrement() so the child's new
indisvalid state is visible.  Do the equivalent for REINDEX
CONCURRENTLY only for the true repair case and at a point with the
required snapshot/catalog-update preconditions.

Add regression tests for plain REINDEX, partial repair with another
invalid sibling, multi-level partition trees, and REINDEX TABLE.
---
 src/backend/catalog/index.c            |  32 ++++
 src/backend/commands/indexcmds.c       |  42 ++++++
 src/backend/commands/tablecmds.c       |   9 +-
 src/include/commands/tablecmds.h       |   2 +
 src/test/regress/expected/indexing.out | 196 +++++++++++++++++++++++++
 src/test/regress/sql/indexing.sql      | 124 ++++++++++++++++
 6 files changed, 402 insertions(+), 3 deletions(-)

diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..30c87148255 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3905,6 +3905,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 9ab74c8df0a..54877d326e7 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"
@@ -3601,6 +3602,7 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		Oid			tableId;
 		Oid			amId;
 		bool		safe;		/* for set_indexsafe_procflags */
+		bool		wasInvalid; /* index was invalid before reindex */
 	} ReindexIndexInfo;
 	List	   *heapRelationIds = NIL;
 	List	   *indexIds = NIL;
@@ -3961,6 +3963,7 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 
 		idx->tableId = RelationGetRelid(heapRel);
 		idx->amId = indexRel->rd_rel->relam;
+		idx->wasInvalid = !indexRel->rd_index->indisvalid;
 
 		/* This function shouldn't be called for temporary relations. */
 		if (indexRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
@@ -4320,6 +4323,45 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 		 * characters.
 		 */
 		CommandCounterIncrement();
+
+		/*
+		 * If a previously-invalid partition index was just replaced by
+		 * a valid one (i.e., the old index was invalid before this
+		 * REINDEX), check whether the parent partitioned index can now
+		 * be marked valid.  We only do this for the "was invalid -> now
+		 * valid" transition to match the non-concurrent path in
+		 * reindex_index().
+		 *
+		 * A snapshot is needed because validatePartitionedIndex() may
+		 * update pg_index via CatalogTupleUpdate(), which requires an
+		 * active snapshot.  This is safe: the CCI above has made the
+		 * swap visible, so the snapshot will see the new index in its
+		 * correct partition position.
+		 */
+		if (oldidx->wasInvalid && get_rel_relispartition(newidx->indexId))
+		{
+			Oid			parentIdxId;
+
+			parentIdxId = get_partition_parent(newidx->indexId, true);
+			if (OidIsValid(parentIdxId))
+			{
+				Relation	parentIdx;
+				Relation	parentTbl;
+
+				PushActiveSnapshot(GetTransactionSnapshot());
+
+				parentIdx = index_open(parentIdxId, AccessShareLock);
+				parentTbl = table_open(parentIdx->rd_index->indrelid,
+									   AccessShareLock);
+
+				validatePartitionedIndex(parentIdx, parentTbl);
+
+				table_close(parentTbl, AccessShareLock);
+				index_close(parentIdx, AccessShareLock);
+
+				PopActiveSnapshot();
+			}
+		}
 	}
 
 	/* Commit this transaction and make index swaps visible */
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index eec09ba1ded..ecca22effb3 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);
@@ -22062,12 +22061,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 c3d8518cb62..84983fb7e95 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 */
diff --git a/src/test/regress/expected/indexing.out b/src/test/regress/expected/indexing.out
index f50868ca6a6..05193401315 100644
--- a/src/test/regress/expected/indexing.out
+++ b/src/test/regress/expected/indexing.out
@@ -1591,6 +1591,202 @@ select indexrelid::regclass, indisvalid,
 (5 rows)
 
 drop table parted_isvalid_tab;
+-- Check that REINDEX of an invalid partition index validates the parent
+-- partitioned index (and recurses upward through multi-level hierarchies).
+-- Test: basic REINDEX INDEX repairs invalid child and validates parent.
+create table reindex_pv (a int, b int) partition by range (a);
+create table reindex_pv_1 partition of reindex_pv for values from (1) to (10);
+-- Create an invalid index on the partition via a failed concurrent build.
+insert into reindex_pv_1 values (1, 0);
+create index concurrently reindex_pv_1_idx on reindex_pv_1 ((a / b));
+ERROR:  division by zero
+-- Create the partitioned index; it picks up the invalid child, so both
+-- parent and child are invalid.
+create index reindex_pv_idx on reindex_pv ((a / b));
+select indexrelid::regclass, indisvalid
+  from pg_index where indexrelid::regclass::text like 'reindex_pv%'
+  order by indexrelid::regclass::text collate "C";
+    indexrelid    | indisvalid 
+------------------+------------
+ reindex_pv_1_idx | f
+ reindex_pv_idx   | f
+(2 rows)
+
+-- Fix the data and REINDEX the child.  Use TRUNCATE (not DELETE) so that
+-- dead tuples with the error-causing expression are physically removed;
+-- REINDEX evaluates expressions on all heap tuples including recently-dead
+-- ones, and concurrent test sessions can prevent VACUUM from cleaning them.
+truncate reindex_pv_1;
+reindex index reindex_pv_1_idx;
+-- Both child and parent should now be valid.
+select indexrelid::regclass, indisvalid
+  from pg_index where indexrelid::regclass::text like 'reindex_pv%'
+  order by indexrelid::regclass::text collate "C";
+    indexrelid    | indisvalid 
+------------------+------------
+ reindex_pv_1_idx | t
+ reindex_pv_idx   | t
+(2 rows)
+
+drop table reindex_pv;
+-- Test: negative case — parent stays invalid while any child remains invalid.
+create table reindex_pv2 (a int, b int) partition by range (a);
+create table reindex_pv2_1 partition of reindex_pv2 for values from (1) to (10);
+create table reindex_pv2_2 partition of reindex_pv2 for values from (10) to (20);
+-- Create invalid indexes on both children.
+insert into reindex_pv2_1 values (1, 0);
+create index concurrently reindex_pv2_1_idx on reindex_pv2_1 ((a / b));
+ERROR:  division by zero
+insert into reindex_pv2_2 values (10, 0);
+create index concurrently reindex_pv2_2_idx on reindex_pv2_2 ((a / b));
+ERROR:  division by zero
+-- Parent picks up both invalid children.
+create index reindex_pv2_idx on reindex_pv2 ((a / b));
+-- Fix only child 1 and reindex it.
+truncate reindex_pv2_1;
+reindex index reindex_pv2_1_idx;
+-- Parent must remain invalid because child 2 is still invalid.
+select indexrelid::regclass, indisvalid
+  from pg_index where indexrelid::regclass::text like 'reindex_pv2%'
+  order by indexrelid::regclass::text collate "C";
+    indexrelid     | indisvalid 
+-------------------+------------
+ reindex_pv2_1_idx | t
+ reindex_pv2_2_idx | f
+ reindex_pv2_idx   | f
+(3 rows)
+
+-- Now fix child 2 as well.
+truncate reindex_pv2_2;
+reindex index reindex_pv2_2_idx;
+-- Now the parent should be valid.
+select indexrelid::regclass, indisvalid
+  from pg_index where indexrelid::regclass::text like 'reindex_pv2%'
+  order by indexrelid::regclass::text collate "C";
+    indexrelid     | indisvalid 
+-------------------+------------
+ reindex_pv2_1_idx | t
+ reindex_pv2_2_idx | t
+ reindex_pv2_idx   | t
+(3 rows)
+
+drop table reindex_pv2;
+-- Test: multi-level hierarchy — validity propagates upward through all levels.
+create table reindex_pv3 (a int, b int) partition by range (a);
+create table reindex_pv3_mid partition of reindex_pv3
+  for values from (1) to (20) partition by range (a);
+create table reindex_pv3_mid_1 partition of reindex_pv3_mid
+  for values from (1) to (10);
+create table reindex_pv3_mid_2 partition of reindex_pv3_mid
+  for values from (10) to (20);
+create table reindex_pv3_other partition of reindex_pv3
+  for values from (20) to (30);
+-- Create an invalid index only on one leaf.
+insert into reindex_pv3_mid_1 values (1, 0);
+create index concurrently reindex_pv3_mid_1_idx on reindex_pv3_mid_1 ((a / b));
+ERROR:  division by zero
+-- Create the top-level partitioned index.  The invalid leaf makes the
+-- intermediate and root indexes invalid as well.
+create index reindex_pv3_idx on reindex_pv3 ((a / b));
+select indexrelid::regclass, indisvalid,
+       indrelid::regclass, inhparent::regclass
+  from pg_index idx left join
+       pg_inherits inh on (idx.indexrelid = inh.inhrelid)
+  where indexrelid::regclass::text like 'reindex_pv3%'
+  order by indexrelid::regclass::text collate "C";
+         indexrelid         | indisvalid |     indrelid      |        inhparent         
+----------------------------+------------+-------------------+--------------------------
+ reindex_pv3_idx            | f          | reindex_pv3       | 
+ reindex_pv3_mid_1_idx      | f          | reindex_pv3_mid_1 | reindex_pv3_mid_expr_idx
+ reindex_pv3_mid_2_expr_idx | t          | reindex_pv3_mid_2 | reindex_pv3_mid_expr_idx
+ reindex_pv3_mid_expr_idx   | f          | reindex_pv3_mid   | reindex_pv3_idx
+ reindex_pv3_other_expr_idx | t          | reindex_pv3_other | reindex_pv3_idx
+(5 rows)
+
+-- Fix and reindex the leaf.
+truncate reindex_pv3_mid_1;
+reindex index reindex_pv3_mid_1_idx;
+-- All levels should now be valid (cascading upward).
+select indexrelid::regclass, indisvalid,
+       indrelid::regclass, inhparent::regclass
+  from pg_index idx left join
+       pg_inherits inh on (idx.indexrelid = inh.inhrelid)
+  where indexrelid::regclass::text like 'reindex_pv3%'
+  order by indexrelid::regclass::text collate "C";
+         indexrelid         | indisvalid |     indrelid      |        inhparent         
+----------------------------+------------+-------------------+--------------------------
+ reindex_pv3_idx            | t          | reindex_pv3       | 
+ reindex_pv3_mid_1_idx      | t          | reindex_pv3_mid_1 | reindex_pv3_mid_expr_idx
+ reindex_pv3_mid_2_expr_idx | t          | reindex_pv3_mid_2 | reindex_pv3_mid_expr_idx
+ reindex_pv3_mid_expr_idx   | t          | reindex_pv3_mid   | reindex_pv3_idx
+ reindex_pv3_other_expr_idx | t          | reindex_pv3_other | reindex_pv3_idx
+(5 rows)
+
+drop table reindex_pv3;
+-- Test: REINDEX TABLE on a partition also validates the parent.
+create table reindex_pv4 (a int, b int) partition by range (a);
+create table reindex_pv4_1 partition of reindex_pv4 for values from (1) to (10);
+insert into reindex_pv4_1 values (1, 0);
+create index concurrently reindex_pv4_1_idx on reindex_pv4_1 ((a / b));
+ERROR:  division by zero
+create index reindex_pv4_idx on reindex_pv4 ((a / b));
+-- Also add a regular index for REINDEX TABLE to process.
+create index reindex_pv4_a_idx on reindex_pv4 (a);
+select indexrelid::regclass, indisvalid
+  from pg_index where indexrelid::regclass::text like 'reindex_pv4%'
+  order by indexrelid::regclass::text collate "C";
+     indexrelid      | indisvalid 
+---------------------+------------
+ reindex_pv4_1_a_idx | t
+ reindex_pv4_1_idx   | f
+ reindex_pv4_a_idx   | t
+ reindex_pv4_idx     | f
+(4 rows)
+
+truncate reindex_pv4_1;
+reindex table reindex_pv4_1;
+-- Both the expression index and parent should be valid now.
+select indexrelid::regclass, indisvalid
+  from pg_index where indexrelid::regclass::text like 'reindex_pv4%'
+  order by indexrelid::regclass::text collate "C";
+     indexrelid      | indisvalid 
+---------------------+------------
+ reindex_pv4_1_a_idx | t
+ reindex_pv4_1_idx   | t
+ reindex_pv4_a_idx   | t
+ reindex_pv4_idx     | t
+(4 rows)
+
+drop table reindex_pv4;
+-- Test: REINDEX INDEX CONCURRENTLY also validates the parent.
+create table reindex_pv5 (a int, b int) partition by range (a);
+create table reindex_pv5_1 partition of reindex_pv5 for values from (1) to (10);
+insert into reindex_pv5_1 values (1, 0);
+create index concurrently reindex_pv5_1_idx on reindex_pv5_1 ((a / b));
+ERROR:  division by zero
+create index reindex_pv5_idx on reindex_pv5 ((a / b));
+select indexrelid::regclass, indisvalid
+  from pg_index where indexrelid::regclass::text like 'reindex_pv5%'
+  order by indexrelid::regclass::text collate "C";
+    indexrelid     | indisvalid 
+-------------------+------------
+ reindex_pv5_1_idx | f
+ reindex_pv5_idx   | f
+(2 rows)
+
+truncate reindex_pv5_1;
+reindex index concurrently reindex_pv5_1_idx;
+-- Both should be valid.
+select indexrelid::regclass, indisvalid
+  from pg_index where indexrelid::regclass::text like 'reindex_pv5%'
+  order by indexrelid::regclass::text collate "C";
+    indexrelid     | indisvalid 
+-------------------+------------
+ reindex_pv5_1_idx | t
+ reindex_pv5_idx   | t
+(2 rows)
+
+drop table reindex_pv5;
 -- Check state of replica indexes when attaching a partition.
 begin;
 create table parted_replica_tab (id int not null) partition by range (id);
diff --git a/src/test/regress/sql/indexing.sql b/src/test/regress/sql/indexing.sql
index 129130d04d4..38602db1e22 100644
--- a/src/test/regress/sql/indexing.sql
+++ b/src/test/regress/sql/indexing.sql
@@ -876,6 +876,130 @@ select indexrelid::regclass, indisvalid,
   order by indexrelid::regclass::text collate "C";
 drop table parted_isvalid_tab;
 
+-- Check that REINDEX of an invalid partition index validates the parent
+-- partitioned index (and recurses upward through multi-level hierarchies).
+
+-- Test: basic REINDEX INDEX repairs invalid child and validates parent.
+create table reindex_pv (a int, b int) partition by range (a);
+create table reindex_pv_1 partition of reindex_pv for values from (1) to (10);
+-- Create an invalid index on the partition via a failed concurrent build.
+insert into reindex_pv_1 values (1, 0);
+create index concurrently reindex_pv_1_idx on reindex_pv_1 ((a / b));
+-- Create the partitioned index; it picks up the invalid child, so both
+-- parent and child are invalid.
+create index reindex_pv_idx on reindex_pv ((a / b));
+select indexrelid::regclass, indisvalid
+  from pg_index where indexrelid::regclass::text like 'reindex_pv%'
+  order by indexrelid::regclass::text collate "C";
+-- Fix the data and REINDEX the child.  Use TRUNCATE (not DELETE) so that
+-- dead tuples with the error-causing expression are physically removed;
+-- REINDEX evaluates expressions on all heap tuples including recently-dead
+-- ones, and concurrent test sessions can prevent VACUUM from cleaning them.
+truncate reindex_pv_1;
+reindex index reindex_pv_1_idx;
+-- Both child and parent should now be valid.
+select indexrelid::regclass, indisvalid
+  from pg_index where indexrelid::regclass::text like 'reindex_pv%'
+  order by indexrelid::regclass::text collate "C";
+drop table reindex_pv;
+
+-- Test: negative case — parent stays invalid while any child remains invalid.
+create table reindex_pv2 (a int, b int) partition by range (a);
+create table reindex_pv2_1 partition of reindex_pv2 for values from (1) to (10);
+create table reindex_pv2_2 partition of reindex_pv2 for values from (10) to (20);
+-- Create invalid indexes on both children.
+insert into reindex_pv2_1 values (1, 0);
+create index concurrently reindex_pv2_1_idx on reindex_pv2_1 ((a / b));
+insert into reindex_pv2_2 values (10, 0);
+create index concurrently reindex_pv2_2_idx on reindex_pv2_2 ((a / b));
+-- Parent picks up both invalid children.
+create index reindex_pv2_idx on reindex_pv2 ((a / b));
+-- Fix only child 1 and reindex it.
+truncate reindex_pv2_1;
+reindex index reindex_pv2_1_idx;
+-- Parent must remain invalid because child 2 is still invalid.
+select indexrelid::regclass, indisvalid
+  from pg_index where indexrelid::regclass::text like 'reindex_pv2%'
+  order by indexrelid::regclass::text collate "C";
+-- Now fix child 2 as well.
+truncate reindex_pv2_2;
+reindex index reindex_pv2_2_idx;
+-- Now the parent should be valid.
+select indexrelid::regclass, indisvalid
+  from pg_index where indexrelid::regclass::text like 'reindex_pv2%'
+  order by indexrelid::regclass::text collate "C";
+drop table reindex_pv2;
+
+-- Test: multi-level hierarchy — validity propagates upward through all levels.
+create table reindex_pv3 (a int, b int) partition by range (a);
+create table reindex_pv3_mid partition of reindex_pv3
+  for values from (1) to (20) partition by range (a);
+create table reindex_pv3_mid_1 partition of reindex_pv3_mid
+  for values from (1) to (10);
+create table reindex_pv3_mid_2 partition of reindex_pv3_mid
+  for values from (10) to (20);
+create table reindex_pv3_other partition of reindex_pv3
+  for values from (20) to (30);
+-- Create an invalid index only on one leaf.
+insert into reindex_pv3_mid_1 values (1, 0);
+create index concurrently reindex_pv3_mid_1_idx on reindex_pv3_mid_1 ((a / b));
+-- Create the top-level partitioned index.  The invalid leaf makes the
+-- intermediate and root indexes invalid as well.
+create index reindex_pv3_idx on reindex_pv3 ((a / b));
+select indexrelid::regclass, indisvalid,
+       indrelid::regclass, inhparent::regclass
+  from pg_index idx left join
+       pg_inherits inh on (idx.indexrelid = inh.inhrelid)
+  where indexrelid::regclass::text like 'reindex_pv3%'
+  order by indexrelid::regclass::text collate "C";
+-- Fix and reindex the leaf.
+truncate reindex_pv3_mid_1;
+reindex index reindex_pv3_mid_1_idx;
+-- All levels should now be valid (cascading upward).
+select indexrelid::regclass, indisvalid,
+       indrelid::regclass, inhparent::regclass
+  from pg_index idx left join
+       pg_inherits inh on (idx.indexrelid = inh.inhrelid)
+  where indexrelid::regclass::text like 'reindex_pv3%'
+  order by indexrelid::regclass::text collate "C";
+drop table reindex_pv3;
+
+-- Test: REINDEX TABLE on a partition also validates the parent.
+create table reindex_pv4 (a int, b int) partition by range (a);
+create table reindex_pv4_1 partition of reindex_pv4 for values from (1) to (10);
+insert into reindex_pv4_1 values (1, 0);
+create index concurrently reindex_pv4_1_idx on reindex_pv4_1 ((a / b));
+create index reindex_pv4_idx on reindex_pv4 ((a / b));
+-- Also add a regular index for REINDEX TABLE to process.
+create index reindex_pv4_a_idx on reindex_pv4 (a);
+select indexrelid::regclass, indisvalid
+  from pg_index where indexrelid::regclass::text like 'reindex_pv4%'
+  order by indexrelid::regclass::text collate "C";
+truncate reindex_pv4_1;
+reindex table reindex_pv4_1;
+-- Both the expression index and parent should be valid now.
+select indexrelid::regclass, indisvalid
+  from pg_index where indexrelid::regclass::text like 'reindex_pv4%'
+  order by indexrelid::regclass::text collate "C";
+drop table reindex_pv4;
+
+-- Test: REINDEX INDEX CONCURRENTLY also validates the parent.
+create table reindex_pv5 (a int, b int) partition by range (a);
+create table reindex_pv5_1 partition of reindex_pv5 for values from (1) to (10);
+insert into reindex_pv5_1 values (1, 0);
+create index concurrently reindex_pv5_1_idx on reindex_pv5_1 ((a / b));
+create index reindex_pv5_idx on reindex_pv5 ((a / b));
+select indexrelid::regclass, indisvalid
+  from pg_index where indexrelid::regclass::text like 'reindex_pv5%'
+  order by indexrelid::regclass::text collate "C";
+truncate reindex_pv5_1;
+reindex index concurrently reindex_pv5_1_idx;
+-- Both should be valid.
+select indexrelid::regclass, indisvalid
+  from pg_index where indexrelid::regclass::text like 'reindex_pv5%'
+  order by indexrelid::regclass::text collate "C";
+drop table reindex_pv5;
+
 -- Check state of replica indexes when attaching a partition.
 begin;
 create table parted_replica_tab (id int not null) partition by range (id);
-- 
2.52.0



^ permalink  raw  reply  [nested|flat] 12+ messages in thread

* Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired
  2026-04-09 06:16 [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Mohamed ALi <[email protected]>
  2026-04-11 00:55 ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Haibo Yan <[email protected]>
  2026-04-11 02:18   ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Haibo Yan <[email protected]>
@ 2026-04-11 16:10     ` Sami Imseih <[email protected]>
  2026-04-13 22:18       ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
  0 siblings, 1 reply; 12+ messages in thread

From: Sami Imseih @ 2026-04-11 16:10 UTC (permalink / raw)
  To: Haibo Yan <[email protected]>; +Cc: Mohamed ALi <[email protected]>; [email protected]

Hi,

Thanks the report

> 6. The parent index remains `indisvalid = false` permanently. No SQL
> command can fix it.

Yes, that seems confusing. the indisvalid on a parent partition should reflect
the state of all index partitions; true if all index partitions are
valid, and false
if any index partition is invalid.

I think this can only become an issue in practice with the combination
of CREATING
a parent index ONLY ( because the parent indisvalid is marked as false
initially),
then one of the partition indexes becoming invalid, then the expectation would
be that if we REINEX/re-create the child index, a re-attach of this
index will set
the parent index indisvalid to true.

The state of an invalid parent index ( with all children being valid )
breaks the ON CONFLICT case:
```
DROP TABLE IF EXISTS pt;
CREATE TABLE pt (a int, b int) PARTITION BY RANGE (a);
CREATE TABLE pt_1 PARTITION OF pt FOR VALUES FROM (1) TO (100);
CREATE TABLE pt_2 PARTITION OF pt FOR VALUES FROM (100) TO (200);
CREATE UNIQUE INDEX pt_a_idx ON pt (a);
-- ON CONFLICT works fine here
INSERT INTO pt VALUES (1, 1);
INSERT INTO pt VALUES (1, 1) ON CONFLICT (a) DO UPDATE SET b = EXCLUDED.b + 1;
-- Manually invalidate the parent index
EXPLAIN ANALYZE UPDATE pg_index SET indisvalid = false WHERE
indexrelid = 'pt_a_idx'::regclass;
SELECT c.relname, i.indisvalid, i.indisready FROM pg_class c JOIN
pg_index i ON c.oid = i.indexrelid WHERE c.relname LIKE 'pt_%' ORDER
BY c.relname;
-- Now ON CONFLICT fails now
EXPLAIN ANALYZE INSERT INTO pt VALUES (1, 1) ON CONFLICT (a) DO UPDATE
SET b = EXCLUDED.b + 1;
-- ERROR: there is no unique or exclusion constraint matching the ON
CONFLICT specification
```
because of:
```
/* We require at least one indisvalid index */
if (results == NIL || !foundValid)
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg("there is no unique or exclusion constraint matching the ON
CONFLICT specification")));
``` in infer_arbiter_indexes() in plancat.c

> Bug 1: REINDEX Does Not Validate Parent

I don't think that a REINDEX should attempt to set the parent index indisvalid.
It seems the responsibility for this falls squarely on the ATTACH
PARTITION command,
as it currently does.

Would the right solution here be to try to have the ATTACH PARTITION  check if
the parent index is not valid, then validatePartitionedIndex() ?

````
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index eec09ba1ded..a46af023689 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -22029,6 +22029,14 @@ ATExecAttachPartitionIdx(List **wqueue,
Relation parentIdx, RangeVar *name)
                free_attrmap(attmap);

                validatePartitionedIndex(parentIdx, parentTbl);
+       } else if (!parentIdx->rd_index->indisvalid)
+       {
+               /*
+                * The index is already attached but the parent isn't valid yet.
+                * Check if all partitions now have valid indexes, and if so,
+                * mark the parent index as valid.
+                */
+               validatePartitionedIndex(parentIdx, parentTbl);
        }
````
We can add some additional documentation about this in the
"ATTACH PARTITION index_name" documentation [1]
so users have a way out of this condition ?


[1] [https://www.postgresql.org/docs/current/sql-alterindex.html]

 --
Sami Imseih
Amazon Web Services (AWS)





^ permalink  raw  reply  [nested|flat] 12+ messages in thread

* Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired
  2026-04-09 06:16 [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Mohamed ALi <[email protected]>
  2026-04-11 00:55 ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Haibo Yan <[email protected]>
  2026-04-11 02:18   ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Haibo Yan <[email protected]>
  2026-04-11 16:10     ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Sami Imseih <[email protected]>
@ 2026-04-13 22:18       ` Michael Paquier <[email protected]>
  2026-04-14 16:05         ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
  0 siblings, 1 reply; 12+ messages in thread

From: Michael Paquier @ 2026-04-13 22:18 UTC (permalink / raw)
  To: Sami Imseih <[email protected]>; +Cc: Haibo Yan <[email protected]>; Mohamed ALi <[email protected]>; [email protected]

On Sat, Apr 11, 2026 at 11:10:54AM -0500, Sami Imseih wrote:
> I don't think that a REINDEX should attempt to set the parent index indisvalid.
> It seems the responsibility for this falls squarely on the ATTACH
> PARTITION command,
> as it currently does.

Relying on REINDEX is not optimal, as it would mean that all the
partitioned indexes would need to be updated before flipping the flag.
If the indisvalid flags of the partitions are already true, this would
be a huge waste of resources.

> Would the right solution here be to try to have the ATTACH PARTITION  check if
> the parent index is not valid, then validatePartitionedIndex() ?

This may be a backpatchable thing, even if it requires one to detach
one partition before attaching it again, or attach a fake partition to
force a flip of the flag, before detaching this fake partition.

One better alternative that I could think of is a new flavor of ALTER
TABLE, like a ALTER TABLE foo VALIDATE PARTITION (no partition name
here) focused on flipping the indisvalid flags?  This would not be
backpatchable, but it would make the whole flow lighter by not
requiring a redefinition of one partition.
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, 2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 12+ messages in thread

* Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired
  2026-04-09 06:16 [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Mohamed ALi <[email protected]>
  2026-04-11 00:55 ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Haibo Yan <[email protected]>
  2026-04-11 02:18   ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Haibo Yan <[email protected]>
  2026-04-11 16:10     ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Sami Imseih <[email protected]>
  2026-04-13 22:18       ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
@ 2026-04-14 16:05         ` Michael Paquier <[email protected]>
  2026-04-18 14:37           ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Sami Imseih <[email protected]>
  0 siblings, 1 reply; 12+ messages in thread

From: Michael Paquier @ 2026-04-14 16:05 UTC (permalink / raw)
  To: Sami Imseih <[email protected]>; +Cc: Haibo Yan <[email protected]>; Mohamed ALi <[email protected]>; [email protected]

On Tue, Apr 14, 2026 at 07:18:33AM +0900, Michael Paquier wrote:
> On Sat, Apr 11, 2026 at 11:10:54AM -0500, Sami Imseih wrote:
>> Would the right solution here be to try to have the ATTACH PARTITION  check if
>> the parent index is not valid, then validatePartitionedIndex() ?
> 
> This may be a backpatchable thing, even if it requires one to detach
> one partition before attaching it again, or attach a fake partition to
> force a flip of the flag, before detaching this fake partition.

Actually no.  Yesterday I was looking at that from the angle of using
ALTER TABLE for the job, that requires a partition bound.  Sami has
mentioned me that a repeated ALTER INDEX .. ATTACH PARTITION does not
fail when repeated, so we could just rely on that and enforce a
round of indisvalid across the partitioned index we are working on.

Could you write a patch?  It would be better to have tests with
multiple levels, at least, with a partitioned table being a leaf of
another partitioned table.  I am sure you get the picture, the point
being to recurse across multiple levels.
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, 2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 12+ messages in thread

* Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired
  2026-04-09 06:16 [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Mohamed ALi <[email protected]>
  2026-04-11 00:55 ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Haibo Yan <[email protected]>
  2026-04-11 02:18   ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Haibo Yan <[email protected]>
  2026-04-11 16:10     ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Sami Imseih <[email protected]>
  2026-04-13 22:18       ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
  2026-04-14 16:05         ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
@ 2026-04-18 14:37           ` Sami Imseih <[email protected]>
  2026-04-19 22:07             ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
  0 siblings, 1 reply; 12+ messages in thread

From: Sami Imseih @ 2026-04-18 14:37 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Haibo Yan <[email protected]>; Mohamed ALi <[email protected]>; [email protected]

Hi,

> Could you write a patch?  It would be better to have tests with
> multiple levels, at least, with a partitioned table being a leaf of
> another partitioned table.  I am sure you get the picture, the point
> being to recurse across multiple levels.

Here is the patch with tests. It adds a test for this case using
multi-level partitions and ensures that the parent indexes are
validated once a child index is set to valid. Also, I added the
negative case where only one child index is validated to ensure
that the parent indexes remain invalid.

--
Sami Imseih
Amazon Web Services (AWS)


Attachments:

  [application/octet-stream] v1-0001-Allow-ALTER-INDEX-ATTACH-PARTITION-to-validate-th.patch (8.8K, 2-v1-0001-Allow-ALTER-INDEX-ATTACH-PARTITION-to-validate-th.patch)
  download | inline diff:
From ba7aa4dc0708b8cc4300264501df059709535164 Mon Sep 17 00:00:00 2001
From: Sami Imseih <[email protected]>
Date: Sat, 18 Apr 2026 05:36:38 +0000
Subject: [PATCH v1 1/1] Allow ALTER INDEX ATTACH PARTITION to validate the
 parent index

This causes ALTER INDEX ATTACH PARTITION to validate a parent
index in the case an index is already attached but the parent
is not yet valid. This occurs in cases where a parent index
was created invalid such as with CREATE INDEX ONLY, but was
left invalid after an invalid child index was attached. This
left a situation in which a user cannot bring the parent index
back to valid after fixing the child index.

An invalid parent index is more than just a passive issue, it
causes ON CONFLICT on a partitioned table if the invalid parent
index is used to enforce a unique constraint.

Discussion: http://postgr.es/m/CAGnOmWqi1D9ycBgUeOGf6mOCd2Dcf%3D6sKhbf4sHLs5xAcKVCMQ%40mail.gmail.com
---
 src/backend/commands/tablecmds.c       | 12 ++++-
 src/test/regress/expected/indexing.out | 71 ++++++++++++++++++++++++++
 src/test/regress/sql/indexing.sql      | 49 ++++++++++++++++++
 3 files changed, 131 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index eec09ba1ded..8b46d9556e8 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -21921,7 +21921,9 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 
 	ObjectAddressSet(address, RelationRelationId, RelationGetRelid(partIdx));
 
-	/* Silently do nothing if already in the right state */
+	/*
+	 * Check if the index is already attached to the correct parent.
+	 */
 	currParent = partIdx->rd_rel->relispartition ?
 		get_partition_parent(partIdxId, false) : InvalidOid;
 	if (currParent != RelationGetRelid(parentIdx))
@@ -22030,6 +22032,14 @@ ATExecAttachPartitionIdx(List **wqueue, Relation parentIdx, RangeVar *name)
 
 		validatePartitionedIndex(parentIdx, parentTbl);
 	}
+	else if (!parentIdx->rd_index->indisvalid)
+	{
+		/*
+		 * The index is attached, but the parent is still invalid; see if it
+		 * can be validated now.
+		 */
+		validatePartitionedIndex(parentIdx, parentTbl);
+	}
 
 	relation_close(parentTbl, AccessShareLock);
 	/* keep these locks till commit */
diff --git a/src/test/regress/expected/indexing.out b/src/test/regress/expected/indexing.out
index f50868ca6a6..476bcc4878f 100644
--- a/src/test/regress/expected/indexing.out
+++ b/src/test/regress/expected/indexing.out
@@ -540,6 +540,77 @@ select relname, indisvalid from pg_class join pg_index on indexrelid = oid
  idxpart_a_idx   | t
 (3 rows)
 
+drop table idxpart;
+-- Verify that re-attaching an already-attached partition index can
+-- validate the parent index if it was still invalid, including
+-- indirect ancestors in subpartitions.
+create table idxpart (a int, b int) partition by range (a);
+create table idxpart1 partition of idxpart for values from (0) to (1000) partition by range (a);
+create table idxpart11 partition of idxpart1 for values from (0) to (500);
+-- create parent indexes
+create index on only idxpart ((a/b));
+create index on only idxpart1 ((a/b));
+-- fail, leaves behind an invalid index on the leaf partition
+insert into idxpart11 values (1, 0);
+create index concurrently on idxpart11 ((a/b));
+ERROR:  division by zero
+select relname, indisvalid from pg_class join pg_index on indexrelid = oid
+   where relname like 'idxpart%' order by relname;
+      relname       | indisvalid 
+--------------------+------------
+ idxpart11_expr_idx | f
+ idxpart1_expr_idx  | f
+ idxpart_expr_idx   | f
+(3 rows)
+
+-- attach the indexes; parents stay invalid
+alter index idxpart1_expr_idx attach partition idxpart11_expr_idx;
+alter index idxpart_expr_idx attach partition idxpart1_expr_idx;
+-- fix the index on the leaf partition
+delete from idxpart11 where b = 0;
+reindex index concurrently idxpart11_expr_idx;
+-- reattach the leaf partition index; parents should now be valid
+alter index idxpart1_expr_idx attach partition idxpart11_expr_idx;
+select relname, indisvalid from pg_class join pg_index on indexrelid = oid
+   where relname like 'idxpart%' order by relname;
+      relname       | indisvalid 
+--------------------+------------
+ idxpart11_expr_idx | t
+ idxpart1_expr_idx  | t
+ idxpart_expr_idx   | t
+(3 rows)
+
+drop table idxpart;
+-- Verify that re-attaching does not validate the parent when another
+-- child index is still invalid.
+create table idxpart (a int, b int) partition by range (a);
+create table idxpart1 partition of idxpart for values from (0) to (500);
+create table idxpart2 partition of idxpart for values from (500) to (1000);
+create index on only idxpart ((a/b));
+-- create invalid indexes on both children
+insert into idxpart1 values (1, 0);
+insert into idxpart2 values (501, 0);
+create index concurrently on idxpart1 ((a/b));
+ERROR:  division by zero
+create index concurrently on idxpart2 ((a/b));
+ERROR:  division by zero
+-- attach both; parent stays invalid
+alter index idxpart_expr_idx attach partition idxpart1_expr_idx;
+alter index idxpart_expr_idx attach partition idxpart2_expr_idx;
+-- fix only idxpart1's index, leave idxpart2's still invalid
+delete from idxpart1 where b = 0;
+reindex index concurrently idxpart1_expr_idx;
+-- re-attach the fixed child; parent should stay invalid
+alter index idxpart_expr_idx attach partition idxpart1_expr_idx;
+select relname, indisvalid from pg_class join pg_index on indexrelid = oid
+   where relname like 'idxpart%' order by relname;
+      relname      | indisvalid 
+-------------------+------------
+ idxpart1_expr_idx | t
+ idxpart2_expr_idx | f
+ idxpart_expr_idx  | f
+(3 rows)
+
 drop table idxpart;
 -- verify dependency handling during ALTER TABLE DETACH PARTITION
 create table idxpart (a int) partition by range (a);
diff --git a/src/test/regress/sql/indexing.sql b/src/test/regress/sql/indexing.sql
index 129130d04d4..ba53c56fd9c 100644
--- a/src/test/regress/sql/indexing.sql
+++ b/src/test/regress/sql/indexing.sql
@@ -246,6 +246,55 @@ select relname, indisvalid from pg_class join pg_index on indexrelid = oid
    where relname like 'idxpart%' order by relname;
 drop table idxpart;
 
+-- Verify that re-attaching an already-attached partition index can
+-- validate the parent index if it was still invalid, including
+-- indirect ancestors in subpartitions.
+create table idxpart (a int, b int) partition by range (a);
+create table idxpart1 partition of idxpart for values from (0) to (1000) partition by range (a);
+create table idxpart11 partition of idxpart1 for values from (0) to (500);
+-- create parent indexes
+create index on only idxpart ((a/b));
+create index on only idxpart1 ((a/b));
+-- fail, leaves behind an invalid index on the leaf partition
+insert into idxpart11 values (1, 0);
+create index concurrently on idxpart11 ((a/b));
+select relname, indisvalid from pg_class join pg_index on indexrelid = oid
+   where relname like 'idxpart%' order by relname;
+-- attach the indexes; parents stay invalid
+alter index idxpart1_expr_idx attach partition idxpart11_expr_idx;
+alter index idxpart_expr_idx attach partition idxpart1_expr_idx;
+-- fix the index on the leaf partition
+delete from idxpart11 where b = 0;
+reindex index concurrently idxpart11_expr_idx;
+-- reattach the leaf partition index; parents should now be valid
+alter index idxpart1_expr_idx attach partition idxpart11_expr_idx;
+select relname, indisvalid from pg_class join pg_index on indexrelid = oid
+   where relname like 'idxpart%' order by relname;
+drop table idxpart;
+
+-- Verify that re-attaching does not validate the parent when another
+-- child index is still invalid.
+create table idxpart (a int, b int) partition by range (a);
+create table idxpart1 partition of idxpart for values from (0) to (500);
+create table idxpart2 partition of idxpart for values from (500) to (1000);
+create index on only idxpart ((a/b));
+-- create invalid indexes on both children
+insert into idxpart1 values (1, 0);
+insert into idxpart2 values (501, 0);
+create index concurrently on idxpart1 ((a/b));
+create index concurrently on idxpart2 ((a/b));
+-- attach both; parent stays invalid
+alter index idxpart_expr_idx attach partition idxpart1_expr_idx;
+alter index idxpart_expr_idx attach partition idxpart2_expr_idx;
+-- fix only idxpart1's index, leave idxpart2's still invalid
+delete from idxpart1 where b = 0;
+reindex index concurrently idxpart1_expr_idx;
+-- re-attach the fixed child; parent should stay invalid
+alter index idxpart_expr_idx attach partition idxpart1_expr_idx;
+select relname, indisvalid from pg_class join pg_index on indexrelid = oid
+   where relname like 'idxpart%' order by relname;
+drop table idxpart;
+
 -- verify dependency handling during ALTER TABLE DETACH PARTITION
 create table idxpart (a int) partition by range (a);
 create table idxpart1 (like idxpart);
-- 
2.50.1 (Apple Git-155)



^ permalink  raw  reply  [nested|flat] 12+ messages in thread

* Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired
  2026-04-09 06:16 [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Mohamed ALi <[email protected]>
  2026-04-11 00:55 ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Haibo Yan <[email protected]>
  2026-04-11 02:18   ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Haibo Yan <[email protected]>
  2026-04-11 16:10     ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Sami Imseih <[email protected]>
  2026-04-13 22:18       ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
  2026-04-14 16:05         ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
  2026-04-18 14:37           ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Sami Imseih <[email protected]>
@ 2026-04-19 22:07             ` Michael Paquier <[email protected]>
  2026-04-22 01:47               ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
  0 siblings, 1 reply; 12+ messages in thread

From: Michael Paquier @ 2026-04-19 22:07 UTC (permalink / raw)
  To: Sami Imseih <[email protected]>; +Cc: Haibo Yan <[email protected]>; Mohamed ALi <[email protected]>; [email protected]

On Sat, Apr 18, 2026 at 09:37:48AM -0500, Sami Imseih wrote:
> Here is the patch with tests. It adds a test for this case using
> multi-level partitions and ensures that the parent indexes are
> validated once a child index is set to valid. Also, I added the
> negative case where only one child index is validated to ensure
> that the parent indexes remain invalid.

That looks sensible here, including the test coverage.  Thanks for the
patch!

One thing that I'm tempted to add is more scans to check indisvalid
across these commands, particularly after the individual ATTACH
PARTITION bits on each individual index.

A second thing.  Do you think that it would be worth adding a
partitioned table that has no leaves in some portion of the test?  I
was thinking about a partitioned table called idxpart2 attached to
idxpart in the first part of the test.  I've found this pattern 
usually useful for this area of the code when recursing with
validatePartitionedIndex() from a parent.  I was also thinking about
a partitioned table idxpart3 in the last test block, but as you want
to check that indisvalid is not flipped to true for the parent if a
child is !indisvalid, it would not be adapted.
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, 2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 12+ messages in thread

* Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired
  2026-04-09 06:16 [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Mohamed ALi <[email protected]>
  2026-04-11 00:55 ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Haibo Yan <[email protected]>
  2026-04-11 02:18   ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Haibo Yan <[email protected]>
  2026-04-11 16:10     ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Sami Imseih <[email protected]>
  2026-04-13 22:18       ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
  2026-04-14 16:05         ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
  2026-04-18 14:37           ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Sami Imseih <[email protected]>
  2026-04-19 22:07             ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
@ 2026-04-22 01:47               ` Michael Paquier <[email protected]>
  2026-04-22 10:33                 ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Sami Imseih <[email protected]>
  0 siblings, 1 reply; 12+ messages in thread

From: Michael Paquier @ 2026-04-22 01:47 UTC (permalink / raw)
  To: Sami Imseih <[email protected]>; +Cc: Haibo Yan <[email protected]>; Mohamed ALi <[email protected]>; [email protected]

On Mon, Apr 20, 2026 at 07:07:59AM +0900, Michael Paquier wrote:
> One thing that I'm tempted to add is more scans to check indisvalid
> across these commands, particularly after the individual ATTACH
> PARTITION bits on each individual index.
> 
> A second thing.  Do you think that it would be worth adding a
> partitioned table that has no leaves in some portion of the test?  I
> was thinking about a partitioned table called idxpart2 attached to
> idxpart in the first part of the test.  I've found this pattern 
> usually useful for this area of the code when recursing with
> validatePartitionedIndex() from a parent.  I was also thinking about
> a partitioned table idxpart3 in the last test block, but as you want
> to check that indisvalid is not flipped to true for the parent if a
> child is !indisvalid, it would not be adapted.

Both things have been added to the tests, and applied the result down
to v14.  The patch was able to apply cleanly across the board, without
conflicts.  That's rare, these days..
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, 2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 12+ messages in thread

* Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired
  2026-04-09 06:16 [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Mohamed ALi <[email protected]>
  2026-04-11 00:55 ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Haibo Yan <[email protected]>
  2026-04-11 02:18   ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Haibo Yan <[email protected]>
  2026-04-11 16:10     ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Sami Imseih <[email protected]>
  2026-04-13 22:18       ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
  2026-04-14 16:05         ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
  2026-04-18 14:37           ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Sami Imseih <[email protected]>
  2026-04-19 22:07             ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
  2026-04-22 01:47               ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
@ 2026-04-22 10:33                 ` Sami Imseih <[email protected]>
  2026-04-29 21:36                   ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Mohamed ALi <[email protected]>
  0 siblings, 1 reply; 12+ messages in thread

From: Sami Imseih @ 2026-04-22 10:33 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Haibo Yan <[email protected]>; Mohamed ALi <[email protected]>; [email protected]

> > One thing that I'm tempted to add is more scans to check indisvalid
> > across these commands, particularly after the individual ATTACH
> > PARTITION bits on each individual index.

That works.

> > A second thing.  Do you think that it would be worth adding a
> > partitioned table that has no leaves in some portion of the test?  I
> > was thinking about a partitioned table called idxpart2 attached to
> > idxpart in the first part of the test.  I've found this pattern
> > usually useful for this area of the code when recursing with
> > validatePartitionedIndex() from a parent.

Good idea.

> Both things have been added to the tests, and applied the result down
> to v14.  The patch was able to apply cleanly across the board, without
> conflicts.  That's rare, these days..

Sorry for the late reply, and thanks for getting this committed!

--
Sami Imseih
Amazon Web Services (AWS)





^ permalink  raw  reply  [nested|flat] 12+ messages in thread

* Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired
  2026-04-09 06:16 [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Mohamed ALi <[email protected]>
  2026-04-11 00:55 ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Haibo Yan <[email protected]>
  2026-04-11 02:18   ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Haibo Yan <[email protected]>
  2026-04-11 16:10     ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Sami Imseih <[email protected]>
  2026-04-13 22:18       ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
  2026-04-14 16:05         ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
  2026-04-18 14:37           ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Sami Imseih <[email protected]>
  2026-04-19 22:07             ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
  2026-04-22 01:47               ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
  2026-04-22 10:33                 ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Sami Imseih <[email protected]>
@ 2026-04-29 21:36                   ` Mohamed ALi <[email protected]>
  2026-04-30 23:28                     ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
  0 siblings, 1 reply; 12+ messages in thread

From: Mohamed ALi @ 2026-04-29 21:36 UTC (permalink / raw)
  To: Sami Imseih <[email protected]>; +Cc: Michael Paquier <[email protected]>; Haibo Yan <[email protected]>; [email protected]

Hi Michael, Sami, Haibo,

Thank you all for the reviews and for committing the fix (9d3e094f12).
I'm glad to see this backpatched through PostgreSQL 14.

Sami's targeted approach — only calling validatePartitionedIndex()
when the index is already attached AND the parent is currently
invalid — is clean and avoids the concerns Haibo raised about
turning the no-op path into a generic validation hook. Good call.


I noticed that the commit did not include a documentation update
for ALTER INDEX ATTACH PARTITION.

There is no mention that re-running the command on an already-attached
index will attempt to validate the parent. Users who hit this bug
would have no way to discover the recovery path from the docs alone —
they would need to find the mailing list thread or read the commit
message.

I have attached a small doc-only patch that adds a paragraph to the
ALTER INDEX documentation:

"If the named index is already attached to the altered index, the
command will attempt to validate the parent index if the parent
is currently invalid."

This applies on top of current HEAD (which includes 9d3e094f12).
Since the code change was backpatched to 14, the doc update should
probably be backpatched to the same branches.

Thanks,
Mohamed Ali
AWS RDS


On Wed, Apr 22, 2026 at 3:33 AM Sami Imseih <[email protected]> wrote:
>
> > > One thing that I'm tempted to add is more scans to check indisvalid
> > > across these commands, particularly after the individual ATTACH
> > > PARTITION bits on each individual index.
>
> That works.
>
> > > A second thing.  Do you think that it would be worth adding a
> > > partitioned table that has no leaves in some portion of the test?  I
> > > was thinking about a partitioned table called idxpart2 attached to
> > > idxpart in the first part of the test.  I've found this pattern
> > > usually useful for this area of the code when recursing with
> > > validatePartitionedIndex() from a parent.
>
> Good idea.
>
> > Both things have been added to the tests, and applied the result down
> > to v14.  The patch was able to apply cleanly across the board, without
> > conflicts.  That's rare, these days..
>
> Sorry for the late reply, and thanks for getting this committed!
>
> --
> Sami Imseih
> Amazon Web Services (AWS)


Attachments:

  [application/octet-stream] v1-0001-doc-ALTER-INDEX-ATTACH-PARTITION-validation.patch (1.7K, 2-v1-0001-doc-ALTER-INDEX-ATTACH-PARTITION-validation.patch)
  download | inline diff:
From 164f25122bc2170218828606b9466b873b1ce67b Mon Sep 17 00:00:00 2001
From: Mohamed ALi <[email protected]>
Date: Wed, 29 Apr 2026 13:54:40 -0700
Subject: [PATCH] doc: Document parent index validation in ALTER INDEX ATTACH
 PARTITION

Commit 9d3e094f12 added the ability for ALTER INDEX ... ATTACH
PARTITION to validate a parent partitioned index when the partition
index is already attached but the parent is still invalid.  This
provides a recovery path when a previously invalid partition index
has been repaired via REINDEX.

However, the documentation for ALTER INDEX ATTACH PARTITION was not
updated to describe this behavior.  Users have no way to discover
this recovery path from the docs alone.

Add a paragraph to the ALTER INDEX documentation explaining that
re-running ATTACH PARTITION on an already-attached index will
attempt to validate the parent if it is currently invalid.

Discussion: https://www.postgresql.org/message-id/CAGnOmWqi1D9ycBgUeOGf6mOCd2Dcf%3D6sKhbf4sHLs5xAcKVCMQ%40mail.gmail.com
---
 doc/src/sgml/ref/alter_index.sgml | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/doc/src/sgml/ref/alter_index.sgml b/doc/src/sgml/ref/alter_index.sgml
index 1d42d05..2a119f5 100644
--- a/doc/src/sgml/ref/alter_index.sgml
+++ b/doc/src/sgml/ref/alter_index.sgml
@@ -97,6 +97,14 @@ ALTER INDEX ALL IN TABLESPACE <replaceable class="parameter">name</replaceable>
       index cannot be dropped by itself, and will automatically be dropped
       if its parent index is dropped.
      </para>
+     <para>
+      If the named index is already attached to the altered index, the
+      command will attempt to validate the parent index if the parent is
+      currently invalid.
+     </para>
     </listitem>
    </varlistentry>
 
-- 
2.50.1 (Apple Git-155)



^ permalink  raw  reply  [nested|flat] 12+ messages in thread

* Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired
  2026-04-09 06:16 [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Mohamed ALi <[email protected]>
  2026-04-11 00:55 ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Haibo Yan <[email protected]>
  2026-04-11 02:18   ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Haibo Yan <[email protected]>
  2026-04-11 16:10     ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Sami Imseih <[email protected]>
  2026-04-13 22:18       ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
  2026-04-14 16:05         ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
  2026-04-18 14:37           ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Sami Imseih <[email protected]>
  2026-04-19 22:07             ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
  2026-04-22 01:47               ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Michael Paquier <[email protected]>
  2026-04-22 10:33                 ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Sami Imseih <[email protected]>
  2026-04-29 21:36                   ` Re: [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Mohamed ALi <[email protected]>
@ 2026-04-30 23:28                     ` Michael Paquier <[email protected]>
  0 siblings, 0 replies; 12+ messages in thread

From: Michael Paquier @ 2026-04-30 23:28 UTC (permalink / raw)
  To: Mohamed ALi <[email protected]>; +Cc: Sami Imseih <[email protected]>; Haibo Yan <[email protected]>; [email protected]

On Wed, Apr 29, 2026 at 02:36:09PM -0700, Mohamed ALi wrote:
> I have attached a small doc-only patch that adds a paragraph to the
> ALTER INDEX documentation:
> 
> "If the named index is already attached to the altered index, the
> command will attempt to validate the parent index if the parent
> is currently invalid."

That sounds like a good idea to me to mention this behavior in the
docs, as you are suggesting.  That's less guesses a user would need to
do, just more reading and something we can directly point at.
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, 2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 12+ messages in thread


end of thread, other threads:[~2026-04-30 23:28 UTC | newest]

Thread overview: 12+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2026-04-09 06:16 [PATCH] Fix: Partitioned parent index remains invalid after child indexes are repaired Mohamed ALi <[email protected]>
2026-04-11 00:55 ` Haibo Yan <[email protected]>
2026-04-11 02:18   ` Haibo Yan <[email protected]>
2026-04-11 16:10     ` Sami Imseih <[email protected]>
2026-04-13 22:18       ` Michael Paquier <[email protected]>
2026-04-14 16:05         ` Michael Paquier <[email protected]>
2026-04-18 14:37           ` Sami Imseih <[email protected]>
2026-04-19 22:07             ` Michael Paquier <[email protected]>
2026-04-22 01:47               ` Michael Paquier <[email protected]>
2026-04-22 10:33                 ` Sami Imseih <[email protected]>
2026-04-29 21:36                   ` Mohamed ALi <[email protected]>
2026-04-30 23:28                     ` Michael Paquier <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox