repro-merge-partition-race-v0.patch

text/plain

Filename: repro-merge-partition-race-v0.patch
Type: text/plain
Part: 0
Message: Re: Add SPLIT PARTITION/MERGE PARTITIONS commands

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
Series: patch v0
File+
src/backend/catalog/heap.c 5 1
src/backend/commands/tablecmds.c 14 2
src/test/modules/injection_points/expected/merge.out 60 0
src/test/modules/injection_points/Makefile 1 1
src/test/modules/injection_points/specs/merge.spec 51 0
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index ae2efdc..654b502 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -3495,7 +3495,11 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound)
 		elog(ERROR, "cache lookup failed for relation %u",
 			 RelationGetRelid(rel));
 
-#ifdef USE_ASSERT_CHECKING
+	/*
+	 * Assertion fails during partition getting multiple parents.  Disable the
+	 * assertion, to see what non-assert builds experience.
+	 */
+#if 0
 	{
 		Form_pg_class classForm;
 		bool		isnull;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 8fcb188..48207f9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -95,6 +95,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
+#include "utils/injection_point.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -15825,7 +15826,11 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel, bool ispart
 			 */
 			if (parent_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 			{
-				Assert(child_att->attinhcount == 1);
+				/*
+				 * Assertion fails during partition getting multiple parents.
+				 * Disable, to see what non-assert builds experience.
+				 */
+				/* Assert(child_att->attinhcount == 1); */
 				child_att->attislocal = false;
 			}
 
@@ -20388,7 +20393,14 @@ createPartitionTable(RangeVar *newPartName, Relation modelRel,
 	 * Open the new partition with no lock, because we already have
 	 * AccessExclusiveLock placed there after creation.
 	 */
-	newRel = table_openrv(newPartName, NoLock);
+	INJECTION_POINT("merge-after-create");
+	/*
+	 * For testing, switch to taking a lock.  This solves two problems.
+	 * First, it gets an AcceptInvalidationMessages(), so we actually
+	 * invalidate the search path.  Second, it avoids an assertion failure
+	 * from our lack of lock, so we see what non-assert builds experience.
+	 */
+	newRel = table_openrv(newPartName, AccessExclusiveLock);
 
 	/*
 	 * We intended to create the partition with the same persistence as the
diff --git a/src/test/modules/injection_points/Makefile b/src/test/modules/injection_points/Makefile
index 2ffd2f7..9af45bf 100644
--- a/src/test/modules/injection_points/Makefile
+++ b/src/test/modules/injection_points/Makefile
@@ -9,7 +9,7 @@ PGFILEDESC = "injection_points - facility for injection points"
 REGRESS = injection_points
 REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress
 
-ISOLATION = inplace
+ISOLATION = inplace merge
 
 # The injection points are cluster-wide, so disable installcheck
 NO_INSTALLCHECK = 1
diff --git a/src/test/modules/injection_points/expected/merge.out b/src/test/modules/injection_points/expected/merge.out
new file mode 100644
index 0000000..04920e7
--- /dev/null
+++ b/src/test/modules/injection_points/expected/merge.out
@@ -0,0 +1,60 @@
+Parsed test spec with 3 sessions
+
+starting permutation: merge1 ct2 detach3
+injection_points_attach
+-----------------------
+                       
+(1 row)
+
+step merge1: 
+	SET search_path = target;
+	ALTER TABLE parted MERGE PARTITIONS (part1, part3) INTO part_all;
+	-- only one of *parted should have rows, but both do
+	SELECT a AS new_target_parted FROM target.parted ORDER BY 1;
+	SELECT a AS old_target_parted FROM old_target.parted ORDER BY 1;
+	SELECT a AS new_target_part_all FROM target.part_all ORDER BY 1;
+	SELECT a AS old_target_part_all FROM old_target.part_all ORDER BY 1;
+ <waiting ...>
+step ct2: 
+	ALTER SCHEMA target RENAME TO old_target;
+	CREATE SCHEMA target
+		CREATE TABLE parted (a int) partition by list (a)
+		CREATE TABLE part_all PARTITION OF parted FOR VALUES IN (1, 2, 3, 4)
+
+step detach3: 
+	SELECT injection_points_detach('merge-after-create');
+	SELECT injection_points_wakeup('merge-after-create');
+
+injection_points_detach
+-----------------------
+                       
+(1 row)
+
+injection_points_wakeup
+-----------------------
+                       
+(1 row)
+
+step merge1: <... completed>
+new_target_parted
+-----------------
+                1
+                3
+(2 rows)
+
+old_target_parted
+-----------------
+                1
+                3
+(2 rows)
+
+new_target_part_all
+-------------------
+                  1
+                  3
+(2 rows)
+
+old_target_part_all
+-------------------
+(0 rows)
+
diff --git a/src/test/modules/injection_points/specs/merge.spec b/src/test/modules/injection_points/specs/merge.spec
new file mode 100644
index 0000000..63bcd2b
--- /dev/null
+++ b/src/test/modules/injection_points/specs/merge.spec
@@ -0,0 +1,51 @@
+setup
+{
+	CREATE EXTENSION injection_points;
+	CREATE SCHEMA target
+		CREATE TABLE parted (a int) partition by list (a)
+		CREATE TABLE part1 PARTITION OF parted FOR VALUES IN (1, 2)
+		CREATE TABLE part3 PARTITION OF parted FOR VALUES IN (3, 4);
+	INSERT INTO target.parted VALUES (1),(3);
+}
+teardown
+{
+	DROP SCHEMA target, old_target CASCADE;
+	DROP EXTENSION injection_points;
+}
+
+# MERGE PARTITIONS
+session s1
+setup	{
+	SELECT injection_points_set_local();
+	SELECT injection_points_attach('merge-after-create', 'wait');
+}
+step merge1	{
+	SET search_path = target;
+	ALTER TABLE parted MERGE PARTITIONS (part1, part3) INTO part_all;
+	-- only one of *parted should have rows, but both do
+	SELECT a AS new_target_parted FROM target.parted ORDER BY 1;
+	SELECT a AS old_target_parted FROM old_target.parted ORDER BY 1;
+	SELECT a AS new_target_part_all FROM target.part_all ORDER BY 1;
+	SELECT a AS old_target_part_all FROM old_target.part_all ORDER BY 1;
+}
+
+
+# inject another table via ALTER SCHEMA RENAME
+session s2
+step ct2	{
+	ALTER SCHEMA target RENAME TO old_target;
+	CREATE SCHEMA target
+		CREATE TABLE parted (a int) partition by list (a)
+		CREATE TABLE part_all PARTITION OF parted FOR VALUES IN (1, 2, 3, 4)
+}
+
+
+# injection release
+session s3
+step detach3	{
+	SELECT injection_points_detach('merge-after-create');
+	SELECT injection_points_wakeup('merge-after-create');
+}
+
+
+permutation merge1(detach3) ct2 detach3