From cbcf27dd51f08d9d3103dd98d8f9f1ec2a98ee11 Mon Sep 17 00:00:00 2001
From: Haibo Yan <haibo.yan@apple.com>
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

