From c52841347f12acc351a2b896b1b91054e7edae90 Mon Sep 17 00:00:00 2001
From: Kirill Reshke <reshkekirill@gmail.com>
Date: Fri, 2 Oct 2026 22:45:09 -0300
Subject: [PATCH v2 1/2] Fix reindexdb --jobs --concurrently with indexes of
 the same table

For parallel index-level REINDEX, reindexdb puts the commands for all
the indexes of one table into a single query.  A multi-statement
simple query runs as an implicit transaction block, where REINDEX
CONCURRENTLY is not allowed, so the run failed.  With --concurrently,
send each command separately instead.

Oversight in 47f99a407d.
---
 src/bin/scripts/reindexdb.c          | 43 ++++++++++++++++++++++++++--
 src/bin/scripts/t/090_reindexdb.pl   | 24 ++++++++++++++++
 src/fe_utils/parallel_slot.c         |  2 +-
 src/include/fe_utils/parallel_slot.h |  9 ++++++
 4 files changed, 74 insertions(+), 4 deletions(-)

diff --git a/src/bin/scripts/reindexdb.c b/src/bin/scripts/reindexdb.c
index d7fb16d3c85..d7784de3f55 100644
--- a/src/bin/scripts/reindexdb.c
+++ b/src/bin/scripts/reindexdb.c
@@ -434,7 +434,7 @@ reindex_one_database(ConnParams *cparams, ReindexType type,
 
 		ParallelSlotSetHandler(free_slot, TableCommandResultHandler, NULL);
 		initPQExpBuffer(&sql);
-		if (parallel && process_type == REINDEX_INDEX)
+		if (parallel && process_type == REINDEX_INDEX && !concurrently)
 		{
 			/*
 			 * For parallel index-level REINDEX, the indices of the same table
@@ -455,14 +455,51 @@ reindex_one_database(ConnParams *cparams, ReindexType type,
 									echo, verbose, concurrently, tablespace, &sql);
 			}
 			indices_tables_cell = indices_tables_cell->next;
+			run_reindex_command(free_slot->connection, process_type, objname,
+								echo, &sql);
+		}
+		else if (parallel && process_type == REINDEX_INDEX)
+		{
+			/*
+			 * REINDEX CONCURRENTLY cannot run in a transaction block, so it
+			 * cannot be part of a multi-statement simple query, which would
+			 * be an implicit transaction block.  The indices of the same
+			 * table still have to be processed by the same job, to avoid
+			 * concurrent REINDEX CONCURRENTLY commands on the same table,
+			 * which could deadlock.  So, each command is sent separately,
+			 * waiting for it to complete before sending the next one.
+			 */
+			gen_reindex_command(free_slot->connection, process_type, objname,
+								echo, verbose, concurrently, tablespace, &sql);
+			run_reindex_command(free_slot->connection, process_type, objname,
+								echo, &sql);
+			if (!consumeQueryResult(free_slot))
+				failed = true;
+			while (indices_tables_cell->next &&
+				   indices_tables_cell->val == indices_tables_cell->next->val)
+			{
+				indices_tables_cell = indices_tables_cell->next;
+				cell = cell->next;
+				objname = cell->val;
+				termPQExpBuffer(&sql);
+				initPQExpBuffer(&sql);
+				gen_reindex_command(free_slot->connection, process_type, objname,
+									echo, verbose, concurrently, tablespace, &sql);
+				run_reindex_command(free_slot->connection, process_type, objname,
+									echo, &sql);
+				if (!consumeQueryResult(free_slot))
+					failed = true;
+			}
+			indices_tables_cell = indices_tables_cell->next;
+			ParallelSlotSetIdle(free_slot);
 		}
 		else
 		{
 			gen_reindex_command(free_slot->connection, process_type, objname,
 								echo, verbose, concurrently, tablespace, &sql);
+			run_reindex_command(free_slot->connection, process_type, objname,
+								echo, &sql);
 		}
-		run_reindex_command(free_slot->connection, process_type, objname,
-							echo, &sql);
 		termPQExpBuffer(&sql);
 
 		cell = cell->next;
diff --git a/src/bin/scripts/t/090_reindexdb.pl b/src/bin/scripts/t/090_reindexdb.pl
index ae7d3724464..1013120f312 100644
--- a/src/bin/scripts/t/090_reindexdb.pl
+++ b/src/bin/scripts/t/090_reindexdb.pl
@@ -168,6 +168,30 @@ $node->issues_sql_like(
 	[ 'reindexdb', '--concurrently', '--index' => 'test1x', 'postgres' ],
 	qr/statement: REINDEX INDEX CONCURRENTLY public\.test1x;/,
 	'reindex specific index concurrently');
+
+# Multiple indexes of the same table with parallel jobs must not be
+# batched into a single multi-statement query when using --concurrently,
+# as REINDEX CONCURRENTLY cannot run inside a transaction block.
+$node->safe_psql('postgres',
+	'CREATE TABLE test2 (a int); CREATE INDEX test2x ON test2 (a);'
+	  . ' CREATE INDEX test2y ON test2 (a);');
+$node->command_ok(
+	[
+		'reindexdb', '--jobs' => '2', '--concurrently',
+		'--index' => 'test2x',
+		'--index' => 'test2y',
+		'postgres',
+	],
+	'reindex two indexes of the same table concurrently with two jobs');
+$node->command_ok(
+	[
+		'reindexdb', '--jobs' => '2',
+		'--index' => 'test2x',
+		'--index' => 'test2y',
+		'postgres',
+	],
+	'reindex two indexes of the same table with two jobs');
+
 $node->issues_sql_like(
 	[ 'reindexdb', '--concurrently', '--schema' => 'public', 'postgres' ],
 	qr/statement: REINDEX SCHEMA CONCURRENTLY public;/,
diff --git a/src/fe_utils/parallel_slot.c b/src/fe_utils/parallel_slot.c
index fb9e6cc4ec1..831e0c27878 100644
--- a/src/fe_utils/parallel_slot.c
+++ b/src/fe_utils/parallel_slot.c
@@ -54,7 +54,7 @@ processQueryResult(ParallelSlot *slot, PGresult *result)
  * nothing remains.  If at least one error is encountered, return false.
  * Note that this will block if the connection is busy.
  */
-static bool
+bool
 consumeQueryResult(ParallelSlot *slot)
 {
 	bool		ok = true;
diff --git a/src/include/fe_utils/parallel_slot.h b/src/include/fe_utils/parallel_slot.h
index a6ebe273ce0..6d5972614bf 100644
--- a/src/include/fe_utils/parallel_slot.h
+++ b/src/include/fe_utils/parallel_slot.h
@@ -78,6 +78,15 @@ extern void ParallelSlotsTerminate(ParallelSlotArray *sa);
 
 extern bool ParallelSlotsWaitCompletion(ParallelSlotArray *sa);
 
+/*
+ * Wait for the results of the query currently being processed by the given
+ * slot, and process them with the handler set for this slot.  Returns
+ * false if the query failed, true otherwise.  The slot is not marked as
+ * idle by this function.  Note that this will block if the connection is
+ * busy.
+ */
+extern bool consumeQueryResult(ParallelSlot *slot);
+
 extern bool TableCommandResultHandler(PGresult *res, PGconn *conn,
 									  void *context);
 
-- 
2.55.0

