diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl index 3d1a4ddd5c..dbfa35c610 100644 --- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl +++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl @@ -178,6 +178,17 @@ my $baseUnloggedPath = $node->safe_psql('postgres', ok(-f "$pgdata/${baseUnloggedPath}_init", 'unlogged init fork in base'); ok(-f "$pgdata/$baseUnloggedPath", 'unlogged main fork in base'); +# Create an compressed table to test that pca and pcd file are copied. +$node->safe_psql('postgres', 'CREATE TABLE base_compressed (id int) WITH(compresstype=pglz)'); + +my $baseCompressedPath = $node->safe_psql('postgres', + q{select pg_relation_filepath('base_compressed')}); + +# Make sure compressed relation's address and data files exist +ok(-f "$pgdata/$baseCompressedPath", 'compressed main fork in base'); +ok(-f "$pgdata/${baseCompressedPath}_pca", 'compressed address file in base'); +ok(-f "$pgdata/${baseCompressedPath}_pcd", 'compressed data file in base'); + # Create files that look like temporary relations to ensure they are ignored. my $postgresOid = $node->safe_psql('postgres', q{select oid from pg_database where datname = 'postgres'}); @@ -238,6 +249,14 @@ ok(-f "$tempdir/backup/${baseUnloggedPath}_init", ok( !-f "$tempdir/backup/$baseUnloggedPath", 'unlogged main fork not in backup'); +# Compressed relation fork's address and data should be copied +ok(-f "$tempdir/backup/${baseCompressedPath}", + 'compressed main fork in backup'); +ok(-f "$tempdir/backup/${baseCompressedPath}_pca", + 'compressed address file in backup'); +ok(-f "$tempdir/backup/${baseCompressedPath}_pcd", + 'compressed data file in backup'); + # Temp relations should not be copied. foreach my $filename (@tempRelationFiles) { diff --git a/src/bin/pg_checksums/pg_checksums.c b/src/bin/pg_checksums/pg_checksums.c index dc20122c89..ce411c440c 100644 --- a/src/bin/pg_checksums/pg_checksums.c +++ b/src/bin/pg_checksums/pg_checksums.c @@ -31,6 +31,8 @@ #include "storage/bufpage.h" #include "storage/checksum.h" #include "storage/checksum_impl.h" +#include "storage/page_compression.h" +#include "storage/page_compression_impl.h" static int64 files_scanned = 0; @@ -181,18 +183,67 @@ skipfile(const char *fn) } static void -scan_file(const char *fn, int segmentno) +scan_file(const char *fn, int segmentno, bool is_compressed_datafile) { PGAlignedBlock buf; + PGAlignedCompressChunks pcchunks_buf; PageHeader header = (PageHeader) buf.data; - int f; + int f, + f_pca; BlockNumber blockno; int flags; int64 blocks_written_in_file = 0; + char fn_pca[MAXPGPATH]; + PageCompressHeader *pcmap = (PageCompressHeader *) NULL; + PageCompressAddr *pcaddr = (PageCompressAddr *) NULL; Assert(mode == PG_MODE_ENABLE || mode == PG_MODE_CHECK); + /* init memory map of compression page address file */ + if (is_compressed_datafile) + { + int r; + PageCompressHeader pchdr; + + strlcpy(fn_pca, fn, sizeof(fn_pca)); + fn_pca[strlen(fn_pca) - 1] = 'a'; + + f_pca = open(fn_pca, PG_BINARY | O_RDONLY, 0); + + if (f_pca < 0) + pg_fatal("could not open file \"%s\": %m", fn_pca); + + r = read(f_pca, &pchdr, sizeof(PageCompressHeader)); + if (r != sizeof(PageCompressHeader)) + { + if (r < 0) + pg_fatal("could not read file \"%s\": %m", + fn_pca); + else + pg_fatal("could not read file \"%s\": read %d of %zu", + fn_pca, r, sizeof(PageCompressHeader)); + } + + if (pchdr.chunk_size != BLCKSZ / 2 && + pchdr.chunk_size != BLCKSZ / 4 && + pchdr.chunk_size != BLCKSZ / 8 && + pchdr.chunk_size != BLCKSZ / 16) + { + pg_fatal("the page compression address file header is corrupted in file \"%s\"", + fn_pca); + } + + pcmap = pc_mmap(f_pca, pchdr.chunk_size, true); + if (pcmap == MAP_FAILED) + { + pg_fatal("could not mmap file \"%s\": %m", + fn_pca); + } + + close(f_pca); + } + flags = (mode == PG_MODE_ENABLE) ? O_RDWR : O_RDONLY; f = open(fn, PG_BINARY | flags, 0); @@ -204,18 +255,121 @@ scan_file(const char *fn, int segmentno) for (blockno = 0;; blockno++) { uint16 csum; - int r = read(f, buf.data, BLCKSZ); + int r; + off_t first_pc_chunk_pos = -1; - if (r == 0) - break; - if (r != BLCKSZ) + if (is_compressed_datafile) { - if (r < 0) - pg_fatal("could not read block %u in file \"%s\": %m", - blockno, fn); + char compressed_data_buf[BLCKSZ + BLCKSZ / 2]; + char *compressed_data = compressed_data_buf; + int nbytes, + i, + read_amount, + range; + off_t seekpos; + char *buffer_pos; + + if (blockno >= pcmap->nblocks) + break; + + pcaddr = GetPageCompressAddr(pcmap, pcmap->chunk_size, blockno); + + /* New pages have no checksum yet */ + if (pcaddr->nchunks == 0) + { + blocks_scanned++; + continue; + } + + /* read all compressed chunk for this block */ + for (i = 0; i < pcaddr->nchunks; i++) + { + buffer_pos = pcchunks_buf.data + pcmap->chunk_size * i; + + seekpos = (off_t) OffsetOfPageCompressChunk(pcmap->chunk_size, pcaddr->chunknos[i]); + if (i == 0) + first_pc_chunk_pos = seekpos; + + range = 1; + while (i < pcaddr->nchunks - 1 && pcaddr->chunknos[i + 1] == pcaddr->chunknos[i] + 1) + { + range++; + i++; + } + read_amount = pcmap->chunk_size * range; + + if (lseek(f, seekpos, SEEK_SET) < 0) + pg_fatal("seek failed for block %u in file \"%s\": %m", blockno, fn); + + nbytes = read(f, buffer_pos, read_amount); + + if (nbytes != read_amount) + { + if (nbytes < 0) + pg_fatal("could not read block %u in file \"%s\": %m", + blockno, fn); + else + pg_fatal("could not read block %u in file \"%s\": read %d of %d", + blockno, fn, nbytes, read_amount); + } + } + + r = pcmap->chunk_size * pcaddr->nchunks; + + /* build decompress buffer */ + if (pcaddr->nchunks > 1) + { + for (int i = 0; i < pcaddr->nchunks; i++) + { + memcpy(compressed_data + StoreCapacityPerPageCompressChunk(pcmap->chunk_size) * i, + pcchunks_buf.data + pcmap->chunk_size * i + SizeOfPageCompressChunkHeaderData, + StoreCapacityPerPageCompressChunk(pcmap->chunk_size)); + } + } else - pg_fatal("could not read block %u in file \"%s\": read %d of %d", - blockno, fn, r, BLCKSZ); + compressed_data = pcchunks_buf.data + SizeOfPageCompressChunkHeaderData; + + /* decompress chunk data */ + nbytes = decompress_page(compressed_data, buf.data, pcmap->algorithm); + if (nbytes != BLCKSZ) + { + if (nbytes == -2) + { + pg_fatal("could not recognized compression algorithm %d for file \"%s\"", + pcmap->algorithm, fn); + } + else + { + pg_log_error("could not decompress block %u in file \"%s\": decompressed %d of %d bytes", + blockno, fn, nbytes, BLCKSZ); + + if (mode == PG_MODE_ENABLE) + exit(1); + else + { + blocks_scanned++; + badblocks++; + current_size += r; + continue; + } + } + } + } + else + { + r = read(f, buf.data, BLCKSZ); + + if (r == 0) + break; + if (r != BLCKSZ) + { + if (r < 0) + pg_fatal("could not read block %u in file \"%s\": %m", + blockno, fn); + else + pg_fatal("could not read block %u in file \"%s\": read %d of %d", + blockno, fn, r, BLCKSZ); + } } blocks_scanned++; @@ -244,7 +398,8 @@ scan_file(const char *fn, int segmentno) } else if (mode == PG_MODE_ENABLE) { - int w; + int w, + i; /* * Do not rewrite if the checksum is already set to the expected @@ -258,20 +413,75 @@ scan_file(const char *fn, int segmentno) /* Set checksum in page header */ header->pd_checksum = csum; - /* Seek back to beginning of block */ - if (lseek(f, -BLCKSZ, SEEK_CUR) < 0) - pg_fatal("seek failed for block %u in file \"%s\": %m", blockno, fn); + if (is_compressed_datafile) + { + /* Seek back to beginning of first chunk */ + if (lseek(f, first_pc_chunk_pos + SizeOfPageCompressChunkHeaderData, SEEK_SET) < 0) + { + pg_fatal("seek failed for block %u in file \"%s\": %m", blockno, fn); + } + + /* Write block with checksum */ + w = write(f, header, sizeof(PageHeaderData)); + if (w != sizeof(PageHeaderData)) + { + if (w < 0) + pg_fatal("could not write block %u in file \"%s\": %m", + blockno, fn); + else + pg_fatal("could not write block %u in file \"%s\": wrote %d of %d", + blockno, fn, w, (int) sizeof(PageHeaderData)); + } + + /* write checksum of chunks for this block */ + for (i = 0; i < pcaddr->nchunks; i++) + { + PageCompressChunk *pcchunk; + off_t seekpos; + char *buffer_pos; + + /* calculate checksum of chunk */ + buffer_pos = pcchunks_buf.data + pcmap->chunk_size * i; + pcchunk = (PageCompressChunk *) buffer_pos; - /* Write block with checksum */ - w = write(f, buf.data, BLCKSZ); - if (w != BLCKSZ) + csum = pg_checksum_compress_chunk(buffer_pos, pcmap->chunk_size, pcaddr->chunknos[i]); + pcchunk->checksum = csum; + + /* Seek back to beginning of chunk */ + seekpos = (off_t) OffsetOfPageCompressChunk(pcmap->chunk_size, pcaddr->chunknos[i]); + if (lseek(f, seekpos, SEEK_SET) < 0) + pg_fatal("seek failed for block %u in file \"%s\": %m", blockno, fn); + + /* Write block with checksum */ + w = write(f, pcchunk, SizeOfPageCompressChunkHeaderData); + if (w != SizeOfPageCompressChunkHeaderData) + { + if (w < 0) + pg_fatal("could not write block %u in file \"%s\": %m", + blockno, fn); + else + pg_fatal("could not write block %u in file \"%s\": wrote %d of %d", + blockno, fn, w, (int) SizeOfPageCompressChunkHeaderData); + } + } + } + else { - if (w < 0) - pg_fatal("could not write block %u in file \"%s\": %m", - blockno, fn); - else - pg_fatal("could not write block %u in file \"%s\": wrote %d of %d", - blockno, fn, w, BLCKSZ); + /* Seek back to beginning of block */ + if (lseek(f, -BLCKSZ, SEEK_CUR) < 0) + pg_fatal("seek failed for block %u in file \"%s\": %m", blockno, fn); + + /* Write block with checksum */ + w = write(f, buf.data, BLCKSZ); + if (w != BLCKSZ) + { + if (w < 0) + pg_fatal("could not write block %u in file \"%s\": %m", + blockno, fn); + else + pg_fatal("could not write block %u in file \"%s\": wrote %d of %d", + blockno, fn, w, BLCKSZ); + } } } @@ -295,6 +505,8 @@ scan_file(const char *fn, int segmentno) } close(f); + if (is_compressed_datafile) + pc_munmap(pcmap); } /* @@ -346,17 +558,32 @@ scan_directory(const char *basedir, const char *subdir, bool sizeonly) char *forkpath, *segmentpath; int segmentno = 0; + size_t filenamelength; + bool is_compressed_datafile = false; if (skipfile(de->d_name)) continue; + /* check if is address or data file for compressed relation */ + strlcpy(fnonly, de->d_name, sizeof(fnonly)); + filenamelength = strlen(fnonly); + if (filenamelength >= 4) + { + if (strncmp(fnonly + filenamelength - 4, "_pca", 4) == 0) + continue; + else if (strncmp(fnonly + filenamelength - 4, "_pcd", 4) == 0) + { + is_compressed_datafile = true; + fnonly[filenamelength - 4] = '\0'; + } + } + /* * Cut off at the segment boundary (".") to get the segment number * in order to mix it into the checksum. Then also cut off at the * fork boundary, to get the filenode the file belongs to for * filtering. */ - strlcpy(fnonly, de->d_name, sizeof(fnonly)); segmentpath = strchr(fnonly, '.'); if (segmentpath != NULL) { @@ -382,7 +609,7 @@ scan_directory(const char *basedir, const char *subdir, bool sizeonly) * the items in the data folder. */ if (!sizeonly) - scan_file(fn, segmentno); + scan_file(fn, segmentno, is_compressed_datafile); } #ifndef WIN32 else if (S_ISDIR(st.st_mode) || S_ISLNK(st.st_mode)) diff --git a/src/bin/pg_checksums/t/002_actions.pl b/src/bin/pg_checksums/t/002_actions.pl index 0309cbbaa3..a810f8e1f1 100644 --- a/src/bin/pg_checksums/t/002_actions.pl +++ b/src/bin/pg_checksums/t/002_actions.pl @@ -85,6 +85,84 @@ sub check_relation_corruption return; } +# Utility routine to create and check a compressed table with corrupted checksums +# on a wanted tablespace. Note that this stops and starts the node +# multiple times to perform the checks, leaving the node started +# at the end. +sub check_compressed_relation_corruption +{ + my $node = shift; + my $table = shift; + my $tablespace = shift; + my $pgdata = $node->data_dir; + + $node->safe_psql( + 'postgres', + "CREATE TABLE $table (a int) WITH(compresstype=pglz, autovacuum_enabled=false); + INSERT INTO $table SELECT a FROM generate_series(1,10000)a;"); + + $node->safe_psql('postgres', + "ALTER TABLE " . $table . " SET TABLESPACE " . $tablespace . ";"); + + my $file_corrupted = + $node->safe_psql('postgres', "SELECT pg_relation_filepath('$table');") . "_pcd"; + my $relfilenode_corrupted = $node->safe_psql('postgres', + "SELECT relfilenode FROM pg_class WHERE relname = '$table';"); + + # Set page header and block size + my $pageheader_size = 24; + my $block_size = $node->safe_psql('postgres', 'SHOW block_size;'); + $node->stop; + + # Checksums are correct for single relfilenode as the table is not + # corrupted yet. + command_ok( + [ + 'pg_checksums', '--check', + '-D', $pgdata, + '--filenode', $relfilenode_corrupted + ], + "succeeds for single relfilenode on tablespace $tablespace with offline cluster" + ); + + # Time to create some corruption + open my $file, '+<', "$pgdata/$file_corrupted"; + seek($file, $pageheader_size, 0); + syswrite($file, "\0\0\0\0\0\0\0\0\0"); + close $file; + + # Checksum checks on single relfilenode fail + $node->command_checks_all( + [ + 'pg_checksums', '--check', + '-D', $pgdata, + '--filenode', $relfilenode_corrupted + ], + 1, + [qr/Bad checksums:.*1/], + [qr/could not decompress block/], + "fails with corrupted data for single relfilenode on tablespace $tablespace" + ); + + # Global checksum checks fail as well + $node->command_checks_all( + [ 'pg_checksums', '--check', '-D', $pgdata ], + 1, + [qr/Bad checksums:.*1/], + [qr/could not decompress block/], + "fails with corrupted data on tablespace $tablespace"); + + # Drop corrupted table again and make sure there is no more corruption. + $node->start; + $node->safe_psql('postgres', "DROP TABLE $table;"); + $node->stop; + $node->command_ok([ 'pg_checksums', '--check', '-D', $pgdata ], + "succeeds again after table drop on tablespace $tablespace"); + + $node->start; + return; +} + # Initialize node with checksums disabled. my $node = PostgreSQL::Test::Cluster->new('node_checksum'); $node->init(); @@ -114,6 +192,15 @@ append_to_file "$pgdata/global/pgsql_tmp/1.1", "foo"; append_to_file "$pgdata/global/pg_internal.init", "foo"; append_to_file "$pgdata/global/pg_internal.init.123", "foo"; +# Create a compressed table and index before vertify enable checksums +$node->start; +$node->safe_psql( + 'postgres', + "CREATE TABLE compressed_table(a int) WITH(compresstype=pglz, autovacuum_enabled=false); + INSERT INTO compressed_table SELECT a FROM generate_series(1,10000)a; + CREATE INDEX ON compressed_table(a) WITH(compresstype=pglz);"); +$node->stop; + # Enable checksums. command_ok([ 'pg_checksums', '--enable', '--no-sync', '-D', $pgdata ], "checksums successfully enabled in cluster"); @@ -195,6 +282,7 @@ command_fails([ 'pg_checksums', '--check', '-D', $pgdata ], # Check corruption of table on default tablespace. check_relation_corruption($node, 'corrupt1', 'pg_default'); +check_compressed_relation_corruption($node, 'compressed_corrupt1', 'pg_default'); # Create tablespace to check corruptions in a non-default tablespace. my $basedir = $node->basedir; @@ -203,6 +291,7 @@ mkdir($tablespace_dir); $node->safe_psql('postgres', "CREATE TABLESPACE ts_corrupt LOCATION '$tablespace_dir';"); check_relation_corruption($node, 'corrupt2', 'ts_corrupt'); +check_compressed_relation_corruption($node, 'compressed_corrupt2', 'ts_corrupt'); # Utility routine to check that pg_checksums is able to detect # correctly-named relation files filled with some corrupted data. diff --git a/src/bin/pg_rewind/t/010_page_compression.pl b/src/bin/pg_rewind/t/010_page_compression.pl new file mode 100644 index 0000000000..59a3dbb238 --- /dev/null +++ b/src/bin/pg_rewind/t/010_page_compression.pl @@ -0,0 +1,204 @@ + +# Copyright (c) 2021-2022, PostgreSQL Global Development Group + +use strict; +use warnings; +use PostgreSQL::Test::Utils; +use Test::More; + +use FindBin; +use lib $FindBin::RealBin; + +use RewindTest; + +sub run_test +{ + my $test_mode = shift; + + RewindTest::setup_cluster($test_mode); + RewindTest::start_primary(); + + # Create a test table and insert a row in primary. + primary_psql("CREATE TABLE tbl1 (d text) WITH(compresstype=pglz, compress_chunk_size=1024, compress_prealloc_chunks=2)"); + primary_psql("INSERT INTO tbl1 VALUES ('in primary')"); + + # This test table will be used to test truncation, i.e. the table + # is extended in the old primary after promotion + primary_psql("CREATE TABLE trunc_tbl (d text) WITH(compresstype=pglz, compress_chunk_size=1024, compress_prealloc_chunks=2)"); + primary_psql("INSERT INTO trunc_tbl VALUES ('in primary')"); + + # This test table will be used to test the "copy-tail" case, i.e. the + # table is truncated in the old primary after promotion + primary_psql("CREATE TABLE tail_tbl (id integer, d text) WITH(compresstype=pglz, compress_chunk_size=1024, compress_prealloc_chunks=2)"); + primary_psql("INSERT INTO tail_tbl VALUES (0, 'in primary')"); + + # This test table is dropped in the old primary after promotion. + primary_psql("CREATE TABLE drop_tbl (d text) WITH(compresstype=pglz, compress_chunk_size=1024, compress_prealloc_chunks=2)"); + primary_psql("INSERT INTO drop_tbl VALUES ('in primary')"); + + primary_psql("CHECKPOINT"); + + RewindTest::create_standby($test_mode); + + # Insert additional data on primary that will be replicated to standby + primary_psql("INSERT INTO tbl1 values ('in primary, before promotion')"); + primary_psql( + "INSERT INTO trunc_tbl values ('in primary, before promotion')"); + primary_psql( + "INSERT INTO tail_tbl SELECT g, 'in primary, before promotion: ' || g FROM generate_series(1, 10000) g" + ); + + primary_psql('CHECKPOINT'); + + RewindTest::promote_standby(); + + # Insert a row in the old primary. This causes the primary and standby + # to have "diverged", it's no longer possible to just apply the + # standy's logs over primary directory - you need to rewind. + primary_psql("INSERT INTO tbl1 VALUES ('in primary, after promotion')"); + + # Also insert a new row in the standby, which won't be present in the + # old primary. + standby_psql("INSERT INTO tbl1 VALUES ('in standby, after promotion')"); + + # Insert enough rows to trunc_tbl to extend the file. pg_rewind should + # truncate it back to the old size. + primary_psql( + "INSERT INTO trunc_tbl SELECT 'in primary, after promotion: ' || g FROM generate_series(1, 10000) g" + ); + + # Truncate tail_tbl. pg_rewind should copy back the truncated part + # (We cannot use an actual TRUNCATE command here, as that creates a + # whole new relfilenode) + primary_psql("DELETE FROM tail_tbl WHERE id > 10"); + primary_psql("VACUUM tail_tbl"); + + # Drop drop_tbl. pg_rewind should copy it back. + primary_psql( + "insert into drop_tbl values ('in primary, after promotion')"); + primary_psql("DROP TABLE drop_tbl"); + + # Create new_tbl in standby. pg_rewind should copy it from source. + standby_psql("CREATE TABLE new_tbl (d text) WITH(compresstype=pglz, compress_chunk_size=1024, compress_prealloc_chunks=2)"); + standby_psql("INSERT INTO new_tbl VALUES ('in standby')"); + + # Before running pg_rewind, do a couple of extra tests with several + # option combinations. As the code paths taken by those tests + # do not change for the "local" and "remote" modes, just run them + # in "local" mode for simplicity's sake. + if ($test_mode eq 'local') + { + my $primary_pgdata = $node_primary->data_dir; + my $standby_pgdata = $node_standby->data_dir; + + # First check that pg_rewind fails if the target cluster is + # not stopped as it fails to start up for the forced recovery + # step. + command_fails( + [ + 'pg_rewind', '--debug', + '--source-pgdata', $standby_pgdata, + '--target-pgdata', $primary_pgdata, + '--no-sync' + ], + 'pg_rewind with running target'); + + # Again with --no-ensure-shutdown, which should equally fail. + # This time pg_rewind complains without attempting to perform + # recovery once. + command_fails( + [ + 'pg_rewind', '--debug', + '--source-pgdata', $standby_pgdata, + '--target-pgdata', $primary_pgdata, + '--no-sync', '--no-ensure-shutdown' + ], + 'pg_rewind --no-ensure-shutdown with running target'); + + # Stop the target, and attempt to run with a local source + # still running. This fails as pg_rewind requires to have + # a source cleanly stopped. + $node_primary->stop; + command_fails( + [ + 'pg_rewind', '--debug', + '--source-pgdata', $standby_pgdata, + '--target-pgdata', $primary_pgdata, + '--no-sync', '--no-ensure-shutdown' + ], + 'pg_rewind with unexpected running source'); + + # Stop the target cluster cleanly, and run again pg_rewind + # with --dry-run mode. If anything gets generated in the data + # folder, the follow-up run of pg_rewind will most likely fail, + # so keep this test as the last one of this subset. + $node_standby->stop; + command_ok( + [ + 'pg_rewind', '--debug', + '--source-pgdata', $standby_pgdata, + '--target-pgdata', $primary_pgdata, + '--no-sync', '--dry-run' + ], + 'pg_rewind --dry-run'); + + # Both clusters need to be alive moving forward. + $node_standby->start; + $node_primary->start; + } + + RewindTest::run_pg_rewind($test_mode); + + check_query( + 'SELECT * FROM tbl1', + qq(in primary +in primary, before promotion +in standby, after promotion +), + 'table content'); + + check_query( + 'SELECT * FROM trunc_tbl', + qq(in primary +in primary, before promotion +), + 'truncation'); + + check_query( + 'SELECT count(*) FROM tail_tbl', + qq(10001 +), + 'tail-copy'); + + check_query( + 'SELECT * FROM drop_tbl', + qq(in primary +), + 'drop'); + + check_query( + 'SELECT * FROM new_tbl', + qq(in standby +), + 'new'); + + # Permissions on PGDATA should be default + SKIP: + { + skip "unix-style permissions not supported on Windows", 1 + if ($windows_os); + + ok(check_mode_recursive($node_primary->data_dir(), 0700, 0600), + 'check PGDATA permissions'); + } + + RewindTest::clean_rewind_test(); + return; +} + +# Run the test in both modes +run_test('local'); +run_test('remote'); +run_test('archive'); + +done_testing(); diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index f265e043e9..5743c28914 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -1278,6 +1278,10 @@ static const char *const table_storage_parameters[] = { "autovacuum_vacuum_insert_threshold", "autovacuum_vacuum_scale_factor", "autovacuum_vacuum_threshold", + "compress_chunk_size", + "compress_prealloc_chunks", + "compresslevel", + "compresstype", "fillfactor", "log_autovacuum_min_duration", "parallel_workers", @@ -2000,6 +2004,8 @@ psql_completion(const char *text, int start, int end) /* ALTER INDEX SET|RESET ( */ else if (Matches("ALTER", "INDEX", MatchAny, "RESET", "(")) COMPLETE_WITH("fillfactor", + "compress_prealloc_chunks", + "compresslevel", "deduplicate_items", /* BTREE */ "fastupdate", "gin_pending_list_limit", /* GIN */ "buffering", /* GiST */ @@ -2007,6 +2013,8 @@ psql_completion(const char *text, int start, int end) ); else if (Matches("ALTER", "INDEX", MatchAny, "SET", "(")) COMPLETE_WITH("fillfactor =", + "compress_prealloc_chunks =", + "compresslevel =", "deduplicate_items =", /* BTREE */ "fastupdate =", "gin_pending_list_limit =", /* GIN */ "buffering =", /* GiST */