v1-0001-Adding-modify-toast-and-test-pg_amcheck.patch.WIP

application/octet-stream

Filename: v1-0001-Adding-modify-toast-and-test-pg_amcheck.patch.WIP
Type: application/octet-stream
Part: 0
Message: Re: pg_amcheck contrib application
From 246e5ea1ec8dad8607ff154cf962fa73ea5356ae Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Mon, 26 Apr 2021 08:12:05 -0700
Subject: [PATCH v1 1/2] Adding modify toast and test pg_amcheck

This commit allows toast data to be modified, and adds tests of
pg_amcheck under corrupted toast conditions.
---
 src/backend/executor/execMain.c           |  7 +--
 src/backend/executor/nodeModifyTable.c    |  6 +-
 src/backend/optimizer/util/appendinfo.c   |  4 +-
 src/bin/pg_amcheck/t/004_verify_heapam.pl | 71 ++++++++++++++++++++++-
 4 files changed, 76 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index df3d7f9a8b..6cda1bfdb6 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -999,6 +999,7 @@ CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
 	{
 		case RELKIND_RELATION:
 		case RELKIND_PARTITIONED_TABLE:
+		case RELKIND_TOASTVALUE:
 			CheckCmdReplicaIdentity(resultRel, operation);
 			break;
 		case RELKIND_SEQUENCE:
@@ -1007,12 +1008,6 @@ CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
 					 errmsg("cannot change sequence \"%s\"",
 							RelationGetRelationName(resultRel))));
 			break;
-		case RELKIND_TOASTVALUE:
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("cannot change TOAST relation \"%s\"",
-							RelationGetRelationName(resultRel))));
-			break;
 		case RELKIND_VIEW:
 
 			/*
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index c5a2a9a054..de84364548 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -2466,7 +2466,8 @@ ExecModifyTable(PlanState *pstate)
 			relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
 			if (relkind == RELKIND_RELATION ||
 				relkind == RELKIND_MATVIEW ||
-				relkind == RELKIND_PARTITIONED_TABLE)
+				relkind == RELKIND_PARTITIONED_TABLE ||
+				relkind == RELKIND_TOASTVALUE)
 			{
 				/* ri_RowIdAttNo refers to a ctid attribute */
 				Assert(AttributeNumberIsValid(resultRelInfo->ri_RowIdAttNo));
@@ -2825,7 +2826,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 			relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
 			if (relkind == RELKIND_RELATION ||
 				relkind == RELKIND_MATVIEW ||
-				relkind == RELKIND_PARTITIONED_TABLE)
+				relkind == RELKIND_PARTITIONED_TABLE ||
+				relkind == RELKIND_TOASTVALUE)
 			{
 				resultRelInfo->ri_RowIdAttNo =
 					ExecFindJunkAttributeInTlist(subplan->targetlist, "ctid");
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index af46f581ac..43c81d9ed7 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -866,7 +866,9 @@ add_row_identity_columns(PlannerInfo *root, Index rtindex,
 
 	if (relkind == RELKIND_RELATION ||
 		relkind == RELKIND_MATVIEW ||
-		relkind == RELKIND_PARTITIONED_TABLE)
+		relkind == RELKIND_PARTITIONED_TABLE ||
+		relkind == RELKIND_TOASTVALUE
+		)
 	{
 		/*
 		 * Emit CTID so that executor can find the row to update or delete.
diff --git a/src/bin/pg_amcheck/t/004_verify_heapam.pl b/src/bin/pg_amcheck/t/004_verify_heapam.pl
index b842f7bc6d..4d66eb64fe 100644
--- a/src/bin/pg_amcheck/t/004_verify_heapam.pl
+++ b/src/bin/pg_amcheck/t/004_verify_heapam.pl
@@ -225,7 +225,7 @@ my $rel = $node->safe_psql('postgres', qq(SELECT pg_relation_filepath('public.te
 my $relpath = "$pgdata/$rel";
 
 # Insert data and freeze public.test
-use constant ROWCOUNT => 16;
+use constant ROWCOUNT => 100;
 $node->safe_psql('postgres', qq(
 	INSERT INTO public.test (a, b, c)
 		VALUES (
@@ -241,6 +241,13 @@ my $relfrozenxid = $node->safe_psql('postgres',
 my $datfrozenxid = $node->safe_psql('postgres',
 	q(select datfrozenxid from pg_database where datname = 'postgres'));
 
+# Find our toast relation name
+my $toastname = $node->safe_psql('postgres', qq(
+	SELECT c.reltoastrelid::regclass
+		FROM pg_catalog.pg_class c
+		WHERE c.oid = 'public.test'::regclass
+		));
+
 # Sanity check that our 'test' table has a relfrozenxid newer than the
 # datfrozenxid for the database, and that the datfrozenxid is greater than the
 # first normal xid.  We rely on these invariants in some of our tests.
@@ -297,7 +304,7 @@ close($file)
 $node->start;
 
 # Ok, Xids and page layout look ok.  We can run corruption tests.
-plan tests => 19;
+plan tests => 38;
 
 # Check that pg_amcheck runs against the uncorrupted table without error.
 $node->command_ok(['pg_amcheck', '-p', $port, 'postgres'],
@@ -339,11 +346,12 @@ sub header
 # performing any remaining checks, so we can't exercise the system properly if
 # we focus all our corruption on a single tuple.
 #
-my @expected;
+my (@expected, @corruptions);
 open($file, '+<', $relpath)
 	or BAIL_OUT("open failed: $!");
 binmode $file;
 
+my $value_id_to_corrupt;
 for (my $tupidx = 0; $tupidx < ROWCOUNT; $tupidx++)
 {
 	my $offnum = $tupidx + 1;  # offnum is 1-based, not zero-based
@@ -512,12 +520,69 @@ for (my $tupidx = 0; $tupidx < ROWCOUNT; $tupidx++)
 		push @expected,
 			qr/${header}multitransaction ID 4000000000 precedes relation minimum multitransaction ID threshold 1/;
 	}
+	elsif ($offnum == 16)
+	{
+		$value_id_to_corrupt = $tup->{c_va_valueid};
+		push (@corruptions, "UPDATE $toastname SET chunk_seq = chunk_seq + 1000 WHERE chunk_id = $value_id_to_corrupt");
+ 		$header = header(0, $offnum, 2);
+ 		push @expected,
+			qr/${header}toast value 16459 chunk 0 has sequence number 1000, but expected sequence number 0/,
+			qr/${header}toast value 16459 chunk 1 has sequence number 1001, but expected sequence number 1/,
+			qr/${header}toast value 16459 chunk 2 has sequence number 1002, but expected sequence number 2/,
+			qr/${header}toast value 16459 chunk 3 has sequence number 1003, but expected sequence number 3/,
+			qr/${header}toast value 16459 chunk 4 has sequence number 1004, but expected sequence number 4/,
+			qr/${header}toast value 16459 chunk 5 has sequence number 1005, but expected sequence number 5/;
+ 	}
+	elsif ($offnum == 17)
+	{
+		$value_id_to_corrupt = $tup->{c_va_valueid};
+		push (@corruptions, "UPDATE $toastname SET chunk_seq = chunk_seq * 1000 WHERE chunk_id = $value_id_to_corrupt");
+ 		$header = header(0, $offnum, 2);
+ 		push @expected,
+			qr/${header}toast value $value_id_to_corrupt chunk 1 has sequence number 1000, but expected sequence number 1/,
+			qr/${header}toast value $value_id_to_corrupt chunk 2 has sequence number 2000, but expected sequence number 2/,
+			qr/${header}toast value $value_id_to_corrupt chunk 3 has sequence number 3000, but expected sequence number 3/,
+			qr/${header}toast value $value_id_to_corrupt chunk 4 has sequence number 4000, but expected sequence number 4/,
+			qr/${header}toast value $value_id_to_corrupt chunk 5 has sequence number 5000, but expected sequence number 5/;
+
+	}
+	elsif ($offnum == 18)
+	{
+		$value_id_to_corrupt = $tup->{c_va_valueid};
+		push (@corruptions, "UPDATE $toastname SET chunk_id = (chunk_id::integer + 10000000)::oid WHERE chunk_id = $value_id_to_corrupt");
+ 		$header = header(0, $offnum, 2);
+ 		push @expected,
+			qr/${header}toast value $value_id_to_corrupt not found in toast table/;
+	}
+	elsif ($offnum == 19)
+	{
+		$value_id_to_corrupt = $tup->{c_va_valueid};
+		push (@corruptions, qq(
+INSERT INTO $toastname (chunk_id, chunk_seq, chunk_data)
+	(SELECT chunk_id,
+			10*chunk_seq + 1000,
+			chunk_data
+		FROM $toastname
+		WHERE chunk_id = $value_id_to_corrupt)
+));
+ 		$header = header(0, $offnum, 2);
+ 		push @expected,
+			qr/${header}toast value $value_id_to_corrupt chunk 6 has sequence number 1000, but expected sequence number 6/,
+			qr/${header}toast value $value_id_to_corrupt chunk 7 has sequence number 1010, but expected sequence number 7/,
+			qr/${header}toast value $value_id_to_corrupt chunk 8 has sequence number 1020, but expected sequence number 8/,
+			qr/${header}toast value $value_id_to_corrupt chunk 9 has sequence number 1030, but expected sequence number 9/,
+			qr/${header}toast value $value_id_to_corrupt chunk 10 has sequence number 1040, but expected sequence number 10/,
+			qr/${header}toast value $value_id_to_corrupt chunk 11 has sequence number 1050, but expected sequence number 11/,
+			qr/${header}toast value $value_id_to_corrupt was expected to end at chunk 6, but ended at chunk 12/;
+	}
 	write_tuple($file, $offset, $tup);
 }
 close($file)
 	or BAIL_OUT("close failed: $!");
 $node->start;
 
+$node->safe_psql('postgres', $_) for (@corruptions);
+
 # Run pg_amcheck against the corrupt table with epoch=0, comparing actual
 # corruption messages against the expected messages
 $node->command_checks_all(
-- 
2.21.1 (Apple Git-122.3)