Fix infer_arbiter_index for partitioned tables
authorÁlvaro Herrera <alvherre@kurilemu.de>
Thu, 11 Dec 2025 19:56:37 +0000 (20:56 +0100)
committerÁlvaro Herrera <alvherre@kurilemu.de>
Thu, 11 Dec 2025 19:56:37 +0000 (20:56 +0100)
The fix for concurrent index operations in bc32a12e0db2 started
considering indexes that are not yet marked indisvalid as arbiters for
INSERT ON CONFLICT.  For partitioned tables, this leads to including
indexes that may not exist in partitions, causing a trivially
reproducible "invalid arbiter index list" error to be thrown because of
failure to match the index.  To fix, it suffices to ignore !indisvalid
indexes on partitioned tables.  There should be no risk that the set of
indexes will change for concurrent transactions, because in order for
such an index to be marked valid, an ALTER INDEX ATTACH PARTITION must
run which requires AccessExclusiveLock.

Author: Mihail Nikalayeu <mihailnikalayeu@gmail.com>
Reported-by: Alexander Lakhin <exclusion@gmail.com>
Reviewed-by: Álvaro Herrera <alvherre@kurilemu.de>
Discussion: https://postgr.es/m/17622f79-117a-4a44-aa8e-0374e53faaf0%40gmail.com

src/backend/optimizer/util/plancat.c
src/test/regress/expected/partition_info.out
src/test/regress/sql/partition_info.sql

index e553afb7f01bcf1558b2770afd883ea37cdb4de2..bf45c355b77fc855c65fd6ec81e351ddecb0e78a 100644 (file)
@@ -999,6 +999,15 @@ infer_arbiter_indexes(PlannerInfo *root)
        if (!idxForm->indisready)
            continue;
 
+       /*
+        * Ignore invalid indexes for partitioned tables.  It's possible that
+        * some partitions don't have the index (yet), and then we would not
+        * find a match during ExecInitPartitionInfo.
+        */
+       if (relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
+           !idxForm->indisvalid)
+           continue;
+
        /*
         * Note that we do not perform a check against indcheckxmin (like e.g.
         * get_relation_info()) here to eliminate candidates, because
index 42b6bc77cad0d2c9e1c557ed902f57550232034b..4defa66e5b3dfc9ec41505d449e7f8877b9a27f8 100644 (file)
@@ -349,3 +349,10 @@ SELECT pg_partition_root('ptif_li_child');
 DROP VIEW ptif_test_view;
 DROP MATERIALIZED VIEW ptif_test_matview;
 DROP TABLE ptif_li_parent, ptif_li_child;
+-- Test about selection of arbiter indexes for partitioned tables with
+-- non-valid index on the parent table
+CREATE TABLE pt (a int PRIMARY KEY) PARTITION BY RANGE (a);
+CREATE TABLE p1 PARTITION OF pt FOR VALUES FROM (1) to (2) PARTITION BY RANGE (a);
+CREATE TABLE p1_1 PARTITION OF p1 FOR VALUES FROM (1) TO (2);
+CREATE UNIQUE INDEX ON ONLY p1 (a);
+INSERT INTO p1 VALUES (1) ON CONFLICT (a) DO NOTHING;
index b5060bec7f0e100eceb8cc6ae067080dfda940a6..2ef65292babc0323b89c2e37a469bddda29cb08b 100644 (file)
@@ -127,3 +127,11 @@ SELECT pg_partition_root('ptif_li_child');
 DROP VIEW ptif_test_view;
 DROP MATERIALIZED VIEW ptif_test_matview;
 DROP TABLE ptif_li_parent, ptif_li_child;
+
+-- Test about selection of arbiter indexes for partitioned tables with
+-- non-valid index on the parent table
+CREATE TABLE pt (a int PRIMARY KEY) PARTITION BY RANGE (a);
+CREATE TABLE p1 PARTITION OF pt FOR VALUES FROM (1) to (2) PARTITION BY RANGE (a);
+CREATE TABLE p1_1 PARTITION OF p1 FOR VALUES FROM (1) TO (2);
+CREATE UNIQUE INDEX ON ONLY p1 (a);
+INSERT INTO p1 VALUES (1) ON CONFLICT (a) DO NOTHING;