
# Copyright (c) 2021-2025, PostgreSQL Global Development Group

use strict;
use warnings FATAL => 'all';

use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;

program_help_ok('pg_resetwal');
program_version_ok('pg_resetwal');
program_options_handling_ok('pg_resetwal');

my $node = PostgreSQL::Test::Cluster->new('main');
$node->init;
$node->append_conf('postgresql.conf', 'track_commit_timestamp = on');

command_like([ 'pg_resetwal', '-n', $node->data_dir ],
	qr/checkpoint/, 'pg_resetwal -n produces output');


# Permissions on PGDATA should be default
SKIP:
{
	skip "unix-style permissions not supported on Windows", 1
	  if ($windows_os);

	ok(check_mode_recursive($node->data_dir, 0700, 0600),
		'check PGDATA permissions');
}

command_ok([ 'pg_resetwal', '--pgdata' => $node->data_dir ],
	'pg_resetwal runs');
$node->start;
is($node->safe_psql("postgres", "SELECT 1;"),
	1, 'server running and working after reset');

command_fails_like(
	[ 'pg_resetwal', $node->data_dir ],
	qr/lock file .* exists/,
	'fails if server running');

$node->stop('immediate');
command_fails_like(
	[ 'pg_resetwal', $node->data_dir ],
	qr/database server was not shut down cleanly/,
	'does not run after immediate shutdown');
command_ok(
	[ 'pg_resetwal', '--force', $node->data_dir ],
	'runs after immediate shutdown with force');
$node->start;
is($node->safe_psql("postgres", "SELECT 1;"),
	1, 'server running and working after forced reset');

$node->stop;

# check various command-line handling

# Note: This test intends to check that a nonexistent data directory
# gives a reasonable error message.  Because of the way the code is
# currently structured, you get an error about readings permissions,
# which is perhaps suboptimal, so feel free to update this test if
# this gets improved.
command_fails_like(
	[ 'pg_resetwal', 'foo' ],
	qr/error: could not read permissions of directory/,
	'fails with nonexistent data directory');

command_fails_like(
	[ 'pg_resetwal', 'foo', 'bar' ],
	qr/too many command-line arguments/,
	'fails with too many command-line arguments');

$ENV{PGDATA} = $node->data_dir;    # not used
command_fails_like(
	['pg_resetwal'],
	qr/no data directory specified/,
	'fails with too few command-line arguments');

# error cases
# -c
command_fails_like(
	[ 'pg_resetwal', '-c' => 'foo', $node->data_dir ],
	qr/error: invalid argument for option -c/,
	'fails with incorrect -c option');
command_fails_like(
	[ 'pg_resetwal', '-c' => '10,bar', $node->data_dir ],
	qr/error: invalid argument for option -c/,
	'fails with incorrect -c option part 2');
command_fails_like(
	[ 'pg_resetwal', '-c' => '1,10', $node->data_dir ],
	qr/greater than/,
	'fails with -c ids value 1 part 1');
command_fails_like(
	[ 'pg_resetwal', '-c' => '10,1', $node->data_dir ],
	qr/greater than/,
	'fails with -c value 1 part 2');
# -e
command_fails_like(
	[ 'pg_resetwal', '-e' => 'foo', $node->data_dir ],
	qr/error: invalid argument for option -e/,
	'fails with incorrect -e option');
command_fails_like(
	[ 'pg_resetwal', '-e' => '-1', $node->data_dir ],
	qr/must not be -1/,
	'fails with -e value -1');
# -l
command_fails_like(
	[ 'pg_resetwal', '-l' => 'foo', $node->data_dir ],
	qr/error: invalid argument for option -l/,
	'fails with incorrect -l option');
# -m
command_fails_like(
	[ 'pg_resetwal', '-m' => 'foo', $node->data_dir ],
	qr/error: invalid argument for option -m/,
	'fails with incorrect -m option');
command_fails_like(
	[ 'pg_resetwal', '-m' => '10,bar', $node->data_dir ],
	qr/error: invalid argument for option -m/,
	'fails with incorrect -m option part 2');

# This used to be forbidden, but nextMulti can legitimately be 0 after
# wraparound, so we now accept it in pg_resetwal too.
command_ok([ 'pg_resetwal', '-m' => '0,10', $node->data_dir ],
	'succeeds with -m value 0 part 1');

command_fails_like(
	[ 'pg_resetwal', '-m' => '10,0', $node->data_dir ],
	qr/must not be 0/,
	'fails with -m value 0 part 2');
# -o
command_fails_like(
	[ 'pg_resetwal', '-o' => 'foo', $node->data_dir ],
	qr/error: invalid argument for option -o/,
	'fails with incorrect -o option');
command_fails_like(
	[ 'pg_resetwal', '-o' => '0', $node->data_dir ],
	qr/must not be 0/,
	'fails with -o value 0');
# -O
command_fails_like(
	[ 'pg_resetwal', '-O' => 'foo', $node->data_dir ],
	qr/error: invalid argument for option -O/,
	'fails with incorrect -O option');
command_fails_like(
	[ 'pg_resetwal', '-O' => '-1', $node->data_dir ],
	qr/must be between 0 and 4294967295/,
	'fails with -O value -1');
# --wal-segsize
command_fails_like(
	[ 'pg_resetwal', '--wal-segsize' => 'foo', $node->data_dir ],
	qr/error: invalid value/,
	'fails with incorrect --wal-segsize option');
command_fails_like(
	[ 'pg_resetwal', '--wal-segsize' => '13', $node->data_dir ],
	qr/must be a power/,
	'fails with invalid --wal-segsize value');
# -u
command_fails_like(
	[ 'pg_resetwal', '-u' => 'foo', $node->data_dir ],
	qr/error: invalid argument for option -u/,
	'fails with incorrect -u option');
command_fails_like(
	[ 'pg_resetwal', '-u' => '1', $node->data_dir ],
	qr/must be greater than/,
	'fails with -u value too small');
# -x
command_fails_like(
	[ 'pg_resetwal', '-x' => 'foo', $node->data_dir ],
	qr/error: invalid argument for option -x/,
	'fails with incorrect -x option');
command_fails_like(
	[ 'pg_resetwal', '-x' => '1', $node->data_dir ],
	qr/must be greater than/,
	'fails with -x value too small');

# --char-signedness
command_fails_like(
	[ 'pg_resetwal', '--char-signedness', 'foo', $node->data_dir ],
	qr/error: invalid argument for option --char-signedness/,
	'fails with incorrect --char-signedness option');

# run with control override options

my $out = (run_command([ 'pg_resetwal', '--dry-run', $node->data_dir ]))[0];
$out =~ /^Database block size: *(\d+)$/m or die;
my $blcksz = $1;

my @cmd = ('pg_resetwal', '--pgdata' => $node->data_dir);

# some not-so-critical hardcoded values
push @cmd, '--epoch' => 1;
push @cmd, '--next-wal-file' => '00000001000000320000004B';
push @cmd, '--next-oid' => 100_000;
push @cmd, '--wal-segsize' => 1;

# these use the guidance from the documentation

sub get_slru_files
{
	opendir(my $dh, $node->data_dir . '/' . $_[0]) or die $!;
	my @files = sort grep { /[0-9A-F]+/ } readdir $dh;
	closedir $dh;
	return @files;
}

my (@files, $mult);

@files = get_slru_files('pg_commit_ts');
# XXX: Should there be a multiplier, similar to the other options?
# -c argument is "old,new"
push @cmd,
  '--commit-timestamp-ids' =>
  sprintf("%d,%d", hex($files[0]) == 0 ? 3 : hex($files[0]), hex($files[-1]));

@files = get_slru_files('pg_multixact/offsets');
$mult = 32 * $blcksz / 4;
# --multixact-ids argument is "new,old"
push @cmd,
  '--multixact-ids' => sprintf("%d,%d",
	(hex($files[-1]) + 1) * $mult,
	hex($files[0]) == 0 ? 1 : hex($files[0] * $mult));

@files = get_slru_files('pg_multixact/members');
$mult = 32 * int($blcksz / 20) * 4;
push @cmd, '--multixact-offset' => (hex($files[-1]) + 1) * $mult;

@files = get_slru_files('pg_xact');
$mult = 32 * $blcksz * 4;
push @cmd,
  '--oldest-transaction-id' =>
  (hex($files[0]) == 0 ? 3 : hex($files[0]) * $mult),
  '--next-transaction-id' => ((hex($files[-1]) + 1) * $mult);

command_ok([ @cmd, '--dry-run' ],
	'runs with control override options, dry run');
command_ok(\@cmd, 'runs with control override options');
command_like(
	[ 'pg_resetwal', '--dry-run', $node->data_dir ],
	qr/^Latest checkpoint's NextOID: *100000$/m,
	'spot check that control changes were applied');

$node->start;
ok(1, 'server started after reset');

done_testing();
