From 6f45093233d79bf719b3c6d1df1d2eae971eead2 Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Thu, 26 Aug 2021 11:52:50 -0700
Subject: [PATCH v1] WIP patch to support amcheck of sequences

---
 contrib/amcheck/expected/check_heap.out |   6 +-
 contrib/amcheck/t/001_verify_heapam.pl  | 102 +++++++++++++++++++++++-
 contrib/amcheck/verify_heapam.c         |  10 ++-
 3 files changed, 113 insertions(+), 5 deletions(-)

diff --git a/contrib/amcheck/expected/check_heap.out b/contrib/amcheck/expected/check_heap.out
index ad3086d2aa..c010361025 100644
--- a/contrib/amcheck/expected/check_heap.out
+++ b/contrib/amcheck/expected/check_heap.out
@@ -180,8 +180,10 @@ CREATE SEQUENCE test_sequence;
 SELECT * FROM verify_heapam('test_sequence',
 							startblock := NULL,
 							endblock := NULL);
-ERROR:  cannot check relation "test_sequence"
-DETAIL:  This operation is not supported for sequences.
+ blkno | offnum | attnum | msg 
+-------+--------+--------+-----
+(0 rows)
+
 -- Check that foreign tables are rejected
 CREATE FOREIGN DATA WRAPPER dummy;
 CREATE SERVER dummy_server FOREIGN DATA WRAPPER dummy;
diff --git a/contrib/amcheck/t/001_verify_heapam.pl b/contrib/amcheck/t/001_verify_heapam.pl
index 4f720a7ed0..174a132264 100644
--- a/contrib/amcheck/t/001_verify_heapam.pl
+++ b/contrib/amcheck/t/001_verify_heapam.pl
@@ -8,7 +8,7 @@ use PostgresNode;
 use TestLib;
 
 use Fcntl qw(:seek);
-use Test::More tests => 80;
+use Test::More tests => 133;
 
 my ($node, $result);
 
@@ -21,6 +21,30 @@ $node->append_conf('postgresql.conf', 'autovacuum=off');
 $node->start;
 $node->safe_psql('postgres', q(CREATE EXTENSION amcheck));
 
+#
+# Check a sequence with no corruption
+#
+fresh_test_sequence('test_seq');
+check_all_options_uncorrupted('test_seq', 'sequence');
+
+#
+# Check a corrupt sequence
+#
+corrupt_sequence_relfile('test_seq');
+detects_sequence_corruption("verify_heapam('test_seq')", "corrupted sequence");
+detects_sequence_corruption(
+	"verify_heapam('test_seq', skip := 'all-visible')",
+	"corrupted sequence skipping all-visible");
+detects_sequence_corruption(
+	"verify_heapam('test_seq', skip := 'all-frozen')",
+	"corrupted sequence skipping all-frozen");
+detects_sequence_corruption(
+	"verify_heapam('test_seq', check_toast := false)",
+	"corrupted sequence skipping toast");
+detects_sequence_corruption(
+	"verify_heapam('test_seq', startblock := 0, endblock := 0)",
+	"corrupted sequence checking only block zero");
+
 #
 # Check a table with data loaded but no corruption, freezing, etc.
 #
@@ -110,6 +134,71 @@ sub fresh_test_table
 	));
 }
 
+# (Re)create and populate a test sequence of the given name
+sub fresh_test_sequence
+{
+	my ($seqname) = @_;
+
+	return $node->safe_psql(
+		'postgres', qq(
+		DROP SEQUENCE IF EXISTS $seqname CASCADE;
+		CREATE SEQUENCE $seqname
+			INCREMENT BY 13
+			MINVALUE 17
+			START WITH 23;
+		BEGIN;
+		SELECT nextval('$seqname');
+		COMMIT;
+		ALTER SEQUENCE $seqname RESTART;
+		SELECT setval('$seqname', 40);
+		SELECT nextval('$seqname');
+		SELECT nextval('$seqname');
+		SELECT nextval('$seqname');
+		ROLLBACK;
+		BEGIN;
+		SELECT setval('$seqname', 35);
+		SELECT nextval('$seqname');
+		COMMIT;
+	));
+}
+
+# Stops the test node, corrupts the first page of the named sequence, and
+# restarts the node.
+sub corrupt_sequence_relfile
+{
+	my ($relname) = @_;
+	my $relpath = relation_filepath($relname);
+
+	$node->stop;
+
+	my $fh;
+	open($fh, '+<', $relpath)
+	  or BAIL_OUT("open failed: $!");
+	binmode $fh;
+
+	my $size = (stat($fh))[7];
+
+	# Loop over 2k chunks, to be compatible with all supported page sizes
+	for (my $start = 0; $start < $size; $start += 2048)
+	{
+		# Don't touch the page header
+		seek($fh, $start + 32, SEEK_SET)
+		  or BAIL_OUT("seek failed: $!");
+
+		# Corrupt the remainder of the 2k chunk
+		syswrite(
+			$fh,
+			pack("L*", (0xAAA15550, 0xAAA0D550, 0x00010000,
+						0x00008000, 0x0000800F, 0x001e8000) x 84)
+		) or BAIL_OUT("syswrite failed: $!");
+	}
+
+	close($fh)
+	  or BAIL_OUT("close failed: $!");
+
+	$node->start;
+}
+
 # Stops the test node, corrupts the first page of the named relation, and
 # restarts the node.
 sub corrupt_first_page
@@ -141,6 +230,17 @@ sub corrupt_first_page
 	$node->start;
 }
 
+sub detects_sequence_corruption
+{
+	my ($function, $testname) = @_;
+
+	detects_corruption(
+		$function,
+		$testname,
+		qr/\w+/		# accept any corruption message
+	);
+}
+
 sub detects_heap_corruption
 {
 	my ($function, $testname) = @_;
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 226271923a..85832183e6 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -305,14 +305,20 @@ verify_heapam(PG_FUNCTION_ARGS)
 	 */
 	if (ctx.rel->rd_rel->relkind != RELKIND_RELATION &&
 		ctx.rel->rd_rel->relkind != RELKIND_MATVIEW &&
-		ctx.rel->rd_rel->relkind != RELKIND_TOASTVALUE)
+		ctx.rel->rd_rel->relkind != RELKIND_TOASTVALUE &&
+		ctx.rel->rd_rel->relkind != RELKIND_SEQUENCE)
 		ereport(ERROR,
 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
 				 errmsg("cannot check relation \"%s\"",
 						RelationGetRelationName(ctx.rel)),
 				 errdetail_relkind_not_supported(ctx.rel->rd_rel->relkind)));
 
-	if (ctx.rel->rd_rel->relam != HEAP_TABLE_AM_OID)
+	/*
+	 * Sequences always use heap AM, but they don't show that in the catalogs.
+	 * Other relkinds might be using a different AM, so check.
+	 */
+	if (ctx.rel->rd_rel->relkind != RELKIND_SEQUENCE &&
+		ctx.rel->rd_rel->relam != HEAP_TABLE_AM_OID)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("only heap AM is supported")));
-- 
2.21.1 (Apple Git-122.3)

