From 107efb4cb1e61601b70a5b725b26d0c54f9fbc90 Mon Sep 17 00:00:00 2001
From: Surya Poondla <suryapoondla4@gmail.com>
Date: Mon, 19 Oct 2026 11:53:07 -0700
Subject: [PATCH v3 2/2] Add isolation test for ATTACH PARTITION lock ordering

ALTER TABLE ... ATTACH PARTITION on a table that is itself a partition
must lock the target's ancestors before the target, because a recursing
ALTER TABLE on the root locks the tree from the top down.  Test that
ATTACH waits for the root without holding any of its partitions, using
LOCK TABLE ONLY to open the window between the recursing command's lock
on the root and its locks on the partitions.

A three-level hierarchy is used so that pre-locking only the target's
direct parent would still fail.

Author: Surya Poondla <suryapoondla4@gmail.com>
Reviewed-by: Chao Li <lic@highgo.com>
Discussion: https://postgr.es/m/CFACA0EB-7E6F-4FAA-9ACE-1FC2226D7482@gmail.com
---
 .../expected/partition-attach-lock-order.out  |  9 +++++
 src/test/isolation/isolation_schedule         |  1 +
 .../specs/partition-attach-lock-order.spec    | 39 +++++++++++++++++++
 3 files changed, 49 insertions(+)
 create mode 100644 src/test/isolation/expected/partition-attach-lock-order.out
 create mode 100644 src/test/isolation/specs/partition-attach-lock-order.spec

diff --git a/src/test/isolation/expected/partition-attach-lock-order.out b/src/test/isolation/expected/partition-attach-lock-order.out
new file mode 100644
index 00000000000..eab72c34713
--- /dev/null
+++ b/src/test/isolation/expected/partition-attach-lock-order.out
@@ -0,0 +1,9 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1begin s1lock s2attach s1alter s1commit
+step s1begin: BEGIN;
+step s1lock: LOCK TABLE ONLY attach_lock_root IN ACCESS EXCLUSIVE MODE;
+step s2attach: ALTER TABLE attach_lock_mid2 ATTACH PARTITION attach_lock_new FOR VALUES FROM (0) TO (100); <waiting ...>
+step s1alter: ALTER TABLE attach_lock_root ALTER COLUMN id SET DEFAULT 0;
+step s1commit: COMMIT;
+step s2attach: <... completed>
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index bf9a2037c70..7c1af1c8ba7 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -113,6 +113,7 @@ test: predicate-hash
 test: predicate-gist
 test: predicate-gin
 test: partition-concurrent-attach
+test: partition-attach-lock-order
 test: partition-drop-index-locking
 test: partition-key-update-1
 test: partition-key-update-2
diff --git a/src/test/isolation/specs/partition-attach-lock-order.spec b/src/test/isolation/specs/partition-attach-lock-order.spec
new file mode 100644
index 00000000000..cdd74b30ca3
--- /dev/null
+++ b/src/test/isolation/specs/partition-attach-lock-order.spec
@@ -0,0 +1,39 @@
+# Verify that ALTER TABLE ... ATTACH PARTITION locks all ancestors of the
+# target table, from the root down, before locking the target itself.
+#
+# ATTACH PARTITION needs the partition constraint of its target, which
+# generate_partition_qual() builds by walking up the partition tree and
+# locking each ancestor.  A recursing ALTER TABLE on the root locks the tree
+# from the top down, so if ATTACH locked the target before its ancestors,
+# the two could deadlock.  Three levels are used so that locking only the
+# target's direct parent first would not be enough.
+
+setup
+{
+  CREATE TABLE attach_lock_root (id int) PARTITION BY RANGE (id);
+  CREATE TABLE attach_lock_mid1 PARTITION OF attach_lock_root FOR VALUES FROM (0) TO (1000) PARTITION BY RANGE (id);
+  CREATE TABLE attach_lock_mid2 PARTITION OF attach_lock_mid1 FOR VALUES FROM (0) TO (500) PARTITION BY RANGE (id);
+  CREATE TABLE attach_lock_new (id int);
+}
+
+teardown
+{
+  DROP TABLE attach_lock_new;
+  DROP TABLE attach_lock_root;
+}
+
+# ALTER TABLE ... SET DEFAULT on the root locks the root and then recurses to
+# its partitions.  We can simulate ATTACH PARTITION starting between those
+# steps by locking only the root beforehand.
+session s1
+step s1begin	{ BEGIN; }
+step s1lock		{ LOCK TABLE ONLY attach_lock_root IN ACCESS EXCLUSIVE MODE; }
+step s1alter	{ ALTER TABLE attach_lock_root ALTER COLUMN id SET DEFAULT 0; }
+step s1commit	{ COMMIT; }
+
+session s2
+step s2attach	{ ALTER TABLE attach_lock_mid2 ATTACH PARTITION attach_lock_new FOR VALUES FROM (0) TO (100); }
+
+# s2attach must wait for the root lock without holding a lock on any of the
+# root's partitions, so that s1alter can complete.
+permutation s1begin s1lock s2attach s1alter s1commit
-- 
2.50.1 (Apple Git-155)

