On 4/19/21 10:43 AM, Mark Dilger wrote:
>
>> On Apr 19, 2021, at 5:11 AM, Andrew Dunstan <[email protected]> wrote:
>>
>> I think therefore I'm inclined for now to do nothing for old version
>> compatibility.
> I agree with waiting until the v15 development cycle.
>
>> I would commit the fix for the IPC::Run caching glitch,
>> and version detection
> Thank you.
>
>> I would add a warning if the module is used with
>> a version <= 11.
> Sounds fine for now.
>
>> The original goal of these changes was to allow testing of combinations
>> of different builds with openssl and nss, which doesn't involve old
>> version compatibility.
> Hmm. I think different folks had different goals. My personal interest is
> to write automated tests which spin up older servers, create data that cannot
> be created on newer servers (such as heap tuples with HEAP_MOVED_IN or
> HEAP_MOVED_OFF bits set), upgrade, and test that new code handles the old
> data correctly. I think this is not only useful for our test suites as a
> community, but is also useful for companies providing support services who
> need to reproduce problems that customers are having on clusters that have
> been pg_upgraded across large numbers of postgres versions.
>
>> As far as I know, without any compatibility changes the module is fully
>> compatible with releases 13 and 12, and with releases 11 and 10 so long
>> as you don't want a standby, and with releases 9.6 and 9.5 if you also
>> don't want a backup. That makes it suitable for a lot of testing without
>> any attempt at version compatibility.
>>
>> We can revisit compatibility further in the next release.
> Sounds good.
I'll work on this. Meanwhile FTR here's my latest revision - it's a lot
less invasive of the main module, so it seems much more palatable to me,
and still passes my test down to 7.2.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index e209ea7163..c6086101f2 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -96,6 +96,7 @@ use File::Spec;
use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
+use PostgresVersion;
use RecursiveCopy;
use Socket;
use Test::More;
@@ -127,6 +128,20 @@ INIT
$last_port_assigned = int(rand() * 16384) + 49152;
}
+# Current dev version, for which we have no subclass
+# When a new stable branch is made this and the subclass hierarchy below
+# need to be adjusted.
+my $devtip = 14;
+
+INIT
+{
+ # sanity check to make sure there is a subclass for the last stable branch
+ my $last_child = 'PostgresNodeV_' . ($devtip -1);
+ eval "${last_child}->can('get_new_node') || die('not found');";
+ die "No child package $last_child found" if $@;
+}
+
+
=pod
=head1 METHODS
@@ -347,9 +362,12 @@ about this node.
sub info
{
my ($self) = @_;
+ my $varr = $self->{_pg_version};
+ my $vstr = join('.', @$varr) if ref $varr;
my $_info = '';
open my $fh, '>', \$_info or die;
print $fh "Name: " . $self->name . "\n";
+ print $fh "Version: " . $vstr . "\n" if $vstr;
print $fh "Data directory: " . $self->data_dir . "\n";
print $fh "Backup directory: " . $self->backup_dir . "\n";
print $fh "Archive directory: " . $self->archive_dir . "\n";
@@ -438,7 +456,7 @@ sub init
mkdir $self->backup_dir;
mkdir $self->archive_dir;
- TestLib::system_or_bail('initdb', '-D', $pgdata, '-A', 'trust', '-N',
+ TestLib::system_or_bail('initdb', '-D', $pgdata, ($self->_initdb_flags),
@{ $params{extra} });
TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata,
@{ $params{auth_extra} });
@@ -465,31 +483,36 @@ sub init
if ($params{allows_streaming})
{
- if ($params{allows_streaming} eq "logical")
- {
- print $conf "wal_level = logical\n";
- }
- else
- {
- print $conf "wal_level = replica\n";
- }
- print $conf "max_wal_senders = 10\n";
- print $conf "max_replication_slots = 10\n";
- print $conf "wal_log_hints = on\n";
- print $conf "hot_standby = on\n";
- # conservative settings to ensure we can run multiple postmasters:
- print $conf "shared_buffers = 1MB\n";
- print $conf "max_connections = 10\n";
- # limit disk space consumption, too:
- print $conf "max_wal_size = 128MB\n";
+ $self->_init_streaming($conf, $params{allows_streaming})
}
else
{
- print $conf "wal_level = minimal\n";
- print $conf "max_wal_senders = 0\n";
+ $self->_init_wal_level_minimal($conf);
}
print $conf "port = $port\n";
+
+ $self->_init_network($conf, $use_tcp, $host);
+
+ close $conf;
+
+ chmod($self->group_access ? 0640 : 0600, "$pgdata/postgresql.conf")
+ or die("unable to set permissions for $pgdata/postgresql.conf");
+
+ $self->set_replication_conf if $params{allows_streaming};
+ $self->enable_archiving if $params{has_archiving};
+ return;
+}
+
+
+# methods use in init() which can be overridden in older versions
+
+sub _initdb_flags { return ('-A', 'trust', '-N'); }
+
+sub _init_network
+{
+ my ($self, $conf, $use_tcp, $host) = @_;
+
if ($use_tcp)
{
print $conf "unix_socket_directories = ''\n";
@@ -500,14 +523,36 @@ sub init
print $conf "unix_socket_directories = '$host'\n";
print $conf "listen_addresses = ''\n";
}
- close $conf;
+}
- chmod($self->group_access ? 0640 : 0600, "$pgdata/postgresql.conf")
- or die("unable to set permissions for $pgdata/postgresql.conf");
+sub _init_streaming
+{
+ my ($self, $conf, $allows_streaming) = @_;
- $self->set_replication_conf if $params{allows_streaming};
- $self->enable_archiving if $params{has_archiving};
- return;
+ if ($allows_streaming eq "logical")
+ {
+ print $conf "wal_level = logical\n";
+ }
+ else
+ {
+ print $conf "wal_level = 'replica'\n";
+ }
+ print $conf "max_wal_senders = 10\n";
+ print $conf "max_replication_slots = 10\n";
+ print $conf "wal_log_hints = on\n";
+ print $conf "hot_standby = on\n";
+ # conservative settings to ensure we can run multiple postmasters:
+ print $conf "shared_buffers = 1MB\n";
+ print $conf "max_connections = 10\n";
+ # limit disk space consumption, too:
+ print $conf "max_wal_size = 128MB\n";
+}
+
+sub _init_wal_level_minimal
+{
+ my ($self, $conf) = @_;
+ print $conf "wal_level = minimal\n";
+ print $conf "max_wal_senders = 0\n";
}
=pod
@@ -539,6 +584,54 @@ sub append_conf
=pod
+=item $node->adjust_conf(filename, setting, value, skip_equals)
+
+Modify the names config file with the setting. If the vaue is undefined,
+instead delete the setting.
+
+This will write "$setting = $value\n" in place of the existsing line,
+unless skip_equals is true, in which case it will write
+"$setting $value\n". If the value needs to be quoted it is up to the
+caller to do that.
+
+=cut
+
+sub adjust_conf
+{
+ my ($self, $filename, $setting, $value, $skip_equals) = @_;
+
+ my $conffile = $self->data_dir . '/' . $filename;
+
+ my $contents = TestLib::slurp_file($conffile);
+ my @lines = split(/\n/,$contents);
+ my @result;
+ my $eq = $skip_equals ? '' : '= ';
+ foreach my $line (@lines)
+ {
+ if ($line !~ /^$setting\W/)
+ {
+ push(@result,$line);
+ next;
+ }
+ print "found setting '$setting'\n";
+ if (defined $value)
+ {
+ print "replaced setting '$setting'\n";
+ push(@result,"$setting $eq$value");
+ }
+ }
+ open my $fh, ">", $conffile
+ or croak "could not write \"$conffile\": $!";
+ print $fh join("\n",@result),"\n";;
+ close $fh;
+
+ chmod($self->group_access() ? 0640 : 0600, $conffile)
+ or die("unable to set permissions for $conffile");
+
+}
+
+=pod
+
=item $node->backup(backup_name)
Create a hot backup with B<pg_basebackup> in subdirectory B<backup_name> of
@@ -565,12 +658,14 @@ sub backup
TestLib::system_or_bail(
'pg_basebackup', '-D', $backup_path, '-h',
$self->host, '-p', $self->port, '--checkpoint',
- 'fast', '--no-sync',
+ 'fast', ($self->_backup_sync),
@{ $params{backup_options} });
print "# Backup finished\n";
return;
}
+sub _backup_sync { return ('--no-sync'); }
+
=item $node->backup_fs_hot(backup_name)
Create a backup with a filesystem level copy in subdirectory B<backup_name> of
@@ -725,6 +820,18 @@ sub init_from_backup
qq(
port = $port
));
+ $self->_init_network_append($use_tcp, $host);
+
+ $self->enable_streaming($root_node) if $params{has_streaming};
+ $self->enable_restoring($root_node, $params{standby})
+ if $params{has_restoring};
+ return;
+}
+
+sub _init_network_append
+{
+ my ($self, $use_tcp, $host) = @_;
+
if ($use_tcp)
{
$self->append_conf('postgresql.conf', "listen_addresses = '$host'");
@@ -734,10 +841,6 @@ port = $port
$self->append_conf('postgresql.conf',
"unix_socket_directories = '$host'");
}
- $self->enable_streaming($root_node) if $params{has_streaming};
- $self->enable_restoring($root_node, $params{standby})
- if $params{has_restoring};
- return;
}
=pod
@@ -797,8 +900,8 @@ sub start
# Note: We set the cluster_name here, not in postgresql.conf (in
# sub init) so that it does not get copied to standbys.
- $ret = TestLib::system_log('pg_ctl', '-D', $self->data_dir, '-l',
- $self->logfile, '-o', "--cluster-name=$name", 'start');
+ $ret = TestLib::system_log('pg_ctl', '-w', '-D', $self->data_dir, '-l',
+ $self->logfile, ($self->_cluster_name_opt($name)), 'start');
if ($ret != 0)
{
@@ -812,6 +915,12 @@ sub start
return 1;
}
+sub _cluster_name_opt
+{
+ my ($self, $name) = @_;
+ return ('-o', "--cluster-name=$name");
+}
+
=pod
=item $node->kill9()
@@ -911,7 +1020,7 @@ sub restart
print "### Restarting node \"$name\"\n";
- TestLib::system_or_bail('pg_ctl', '-D', $pgdata, '-l', $logfile,
+ TestLib::system_or_bail('pg_ctl', '-w', '-D', $pgdata, '-l', $logfile,
'restart');
$self->_update_pid(1);
@@ -975,13 +1084,15 @@ sub enable_streaming
print "### Enabling streaming replication for node \"$name\"\n";
$self->append_conf(
- 'postgresql.conf', qq(
+ $self->_recovery_file, qq(
primary_conninfo='$root_connstr'
));
$self->set_standby_mode();
return;
}
+sub _recovery_file { return "postgresql.conf"; }
+
# Internal routine to enable archive recovery command on a standby node
sub enable_restoring
{
@@ -1004,7 +1115,7 @@ sub enable_restoring
: qq{cp "$path/%f" "%p"};
$self->append_conf(
- 'postgresql.conf', qq(
+ $self->_recovery_file, qq(
restore_command = '$copy_command'
));
if ($standby)
@@ -1196,9 +1307,72 @@ sub get_new_node
# Add node to list of nodes
push(@all_nodes, $node);
+ # Get information about the node
+ $node->_read_pg_config;
+
+ # bless the object into the appropriate subclass,
+ # according to the found version
+ if (ref $node->{_pg_version} && $node->{_pg_version} < $devtip )
+ {
+ my $maj = $node->{_pg_version}->[0];
+ my $subclass = __PACKAGE__ . "V_$maj";
+ if ($maj < 10)
+ {
+ $maj = $node->{_pg_version}->[1];
+ $subclass .= "_$maj";
+ }
+ bless $node, $subclass;
+ }
+
return $node;
}
+# Private routine to run the pg_config binary found in our environment (or in
+# our install_path, if we have one), and collect all fields that matter to us.
+#
+sub _read_pg_config
+{
+ my ($self) = @_;
+ my $inst = $self->{_install_path};
+ my $pg_config = "pg_config";
+
+ if (defined $inst)
+ {
+ # If the _install_path is invalid, our PATH variables might find an
+ # unrelated pg_config executable elsewhere. Sanity check the
+ # directory.
+ BAIL_OUT("directory not found: $inst")
+ unless -d $inst;
+
+ # If the directory exists but is not the root of a postgresql
+ # installation, or if the user configured using
+ # --bindir=$SOMEWHERE_ELSE, we're not going to find pg_config, so
+ # complain about that, too.
+ $pg_config = "$inst/bin/pg_config";
+ BAIL_OUT("pg_config not found: $pg_config")
+ unless -e $pg_config;
+ BAIL_OUT("pg_config not executable: $pg_config")
+ unless -x $pg_config;
+
+ # Leave $pg_config install_path qualified, to be sure we get the right
+ # version information, below, or die trying
+ }
+
+ local %ENV = $self->_get_env();
+
+ # We only want the version field
+ open my $fh, "-|", $pg_config, "--version"
+ or
+ BAIL_OUT("$pg_config failed: $!");
+ my $version_line = <$fh>;
+ close $fh or die;
+
+ $self->{_pg_version} = PostgresVersion->new($version_line);
+
+ BAIL_OUT("could not parse pg_config --version output: $version_line")
+ unless defined $self->{_pg_version};
+}
+
# Private routine to return a copy of the environment with the PATH and
# (DY)LD_LIBRARY_PATH correctly set when there is an install path set for
# the node.
@@ -1271,6 +1445,28 @@ sub _get_env
return (%inst_env);
}
+# Private routine to get an installation path qualified command.
+#
+# IPC::Run maintains a cache, %cmd_cache, mapping commands to paths. Tests
+# which use nodes spanning more than one postgres installation path need to
+# avoid confusing which installation's binaries get run. Setting $ENV{PATH} is
+# insufficient, as IPC::Run does not check to see if the path has changed since
+# caching a command.
+sub installed_command
+{
+ my ($self, $cmd) = @_;
+
+ # Nodes using alternate installation locations use their installation's
+ # bin/ directory explicitly
+ return join('/', $self->{_install_path}, 'bin', $cmd)
+ if defined $self->{_install_path};
+
+ # Nodes implicitly using the default installation location rely on IPC::Run
+ # to find the right binary, which should not cause %cmd_cache confusion,
+ # because no nodes with other installation paths do it that way.
+ return $cmd;
+}
+
=pod
=item get_free_port()
@@ -1516,6 +1712,14 @@ is set to true if the psql call times out.
If set, use this as the connection string for the connection to the
backend.
+=item host => B<value>
+
+If this parameter is set, this host is used for the connection attempt.
+
+=item port => B<port>
+
+If this parameter is set, this port is used for the connection attempt.
+
=item replication => B<value>
If set, add B<replication=value> to the conninfo string.
@@ -1568,7 +1772,23 @@ sub psql
}
$psql_connstr .= defined $replication ? " replication=$replication" : "";
- my @psql_params = ('psql', '-XAtq', '-d', $psql_connstr, '-f', '-');
+ my @no_password = ('-w') if ($params{no_password});
+
+ my @host = ('-h', $params{host})
+ if defined $params{host};
+ my @port = ('-p', $params{port})
+ if defined $params{port};
+
+ my @psql_params = (
+ $self->installed_command('psql'),
+ '-XAtq',
+ @no_password,
+ @host,
+ @port,
+ '-d',
+ $psql_connstr,
+ '-f',
+ '-');
# If the caller wants an array and hasn't passed stdout/stderr
# references, allocate temporary ones to capture them so we
@@ -1754,7 +1974,7 @@ sub background_psql
my $replication = $params{replication};
my @psql_params = (
- 'psql',
+ $self->installed_command('psql'),
'-XAtq',
'-d',
$self->connstr($dbname)
@@ -1831,7 +2051,11 @@ sub interactive_psql
local %ENV = $self->_get_env();
- my @psql_params = ('psql', '-XAt', '-d', $self->connstr($dbname));
+ my @psql_params = (
+ $self->installed_command('psql'),
+ '-XAt',
+ '-d',
+ $self->connstr($dbname));
push @psql_params, @{ $params{extra_params} }
if defined $params{extra_params};
@@ -1888,6 +2112,14 @@ If given, it must be an array reference containing a list of regular
expressions that must NOT match against the server log. They will be
passed to C<Test::More::unlike()>.
+=item host => B<value>
+
+If this parameter is set, this host is used for the connection attempt.
+
+=item port => B<port>
+
+If this parameter is set, this port is used for the connection attempt.
+
=back
=cut
@@ -1924,7 +2156,9 @@ sub connect_ok
my ($ret, $stdout, $stderr) = $self->psql(
'postgres',
$sql,
- extra_params => ['-w'],
+ no_password => 1,
+ host => $params{host},
+ port => $params{port},
connstr => "$connstr",
on_error_stop => 0);
@@ -2041,7 +2275,13 @@ sub poll_query_until
$expected = 't' unless defined($expected); # default value
- my $cmd = [ 'psql', '-XAt', '-c', $query, '-d', $self->connstr($dbname) ];
+ my $cmd = [
+ $self->installed_command('psql'),
+ '-XAt',
+ '-c',
+ $query,
+ '-d',
+ $self->connstr($dbname) ];
my ($stdout, $stderr);
my $max_attempts = 180 * 10;
my $attempts = 0;
@@ -2225,13 +2465,7 @@ mode must be specified.
sub lsn
{
my ($self, $mode) = @_;
- my %modes = (
- 'insert' => 'pg_current_wal_insert_lsn()',
- 'flush' => 'pg_current_wal_flush_lsn()',
- 'write' => 'pg_current_wal_lsn()',
- 'receive' => 'pg_last_wal_receive_lsn()',
- 'replay' => 'pg_last_wal_replay_lsn()');
-
+ my %modes = $self->_lsn_mode_map;
$mode = '<undef>' if !defined($mode);
croak "unknown mode for 'lsn': '$mode', valid modes are "
. join(', ', keys %modes)
@@ -2249,6 +2483,16 @@ sub lsn
}
}
+sub _lsn_mode_map
+{
+ return (
+ 'insert' => 'pg_current_wal_insert_lsn()',
+ 'flush' => 'pg_current_wal_flush_lsn()',
+ 'write' => 'pg_current_wal_lsn()',
+ 'receive' => 'pg_last_wal_receive_lsn()',
+ 'replay' => 'pg_last_wal_replay_lsn()');
+}
+
=pod
=item $node->wait_for_catchup(standby_name, mode, target_lsn)
@@ -2295,8 +2539,10 @@ sub wait_for_catchup
}
else
{
- $lsn_expr = 'pg_current_wal_lsn()';
+ my %funcmap = $self->_lsn_mode_map;
+ $lsn_expr = $funcmap{write};
}
+ my $suffix = $self->_replication_suffix;
print "Waiting for replication conn "
. $standby_name . "'s "
. $mode
@@ -2304,13 +2550,16 @@ sub wait_for_catchup
. $lsn_expr . " on "
. $self->name . "\n";
my $query =
- qq[SELECT $lsn_expr <= ${mode}_lsn AND state = 'streaming' FROM pg_catalog.pg_stat_replication WHERE application_name = '$standby_name';];
+ qq[SELECT $lsn_expr <= ${mode}$suffix AND state = 'streaming' FROM pg_catalog.pg_stat_replication WHERE application_name in ('$standby_name', 'walreceiver');];
$self->poll_query_until('postgres', $query)
or croak "timed out waiting for catchup";
print "done\n";
return;
}
+sub _current_lsn_func { return "pg_current_wal_lsn"; }
+sub _replication_suffix { return "_lsn"; }
+
=pod
=item $node->wait_for_slot_catchup(slot_name, mode, target_lsn)
@@ -2461,7 +2710,8 @@ sub pg_recvlogical_upto
croak 'endpos must be specified' unless defined($endpos);
my @cmd = (
- 'pg_recvlogical', '-S', $slot_name, '--dbname',
+ $self->installed_command('pg_recvlogical'),
+ '-S', $slot_name, '--dbname',
$self->connstr($dbname));
push @cmd, '--endpos', $endpos;
push @cmd, '-f', '-', '--no-loop', '--start';
@@ -2528,4 +2778,454 @@ sub pg_recvlogical_upto
=cut
+##########################################################################
+#
+# Subclasses.
+#
+# There should be a subclass for each old version supported. The newest
+# (i.e. the one for the latest stable release) should inherit from the
+# PostgresNode class. Each other subclass should inherit from the subclass
+# repesenting the immediately succeeding stable release.
+#
+# The name must be PostgresNodeV_nn{_nn} where V_nn_{_nn} corresonds to the
+# release number (e.g. V_12 for release 12 or V_9_6 fpr release 9.6.)
+# PostgresNode knows about this naming convention and blesses each node
+# into the appropriate subclass.
+#
+# Each time a new stable release branch is made a subclass should be added
+# that inherits from PostgresNode, and be made the parent of the previous
+# subclass that inherited from PostgresNode.
+#
+# An empty package means that there are no differences that need to be
+# handled between this release and the later release.
+#
+##########################################################################
+
+package PostgresNodeV_13; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNode);
+
+# https://www.postgresql.org/docs/10/release-13.html
+
+##########################################################################
+
+package PostgresNodeV_12; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_13);
+
+# https://www.postgresql.org/docs/12/release-12.html
+
+##########################################################################
+
+package PostgresNodeV_11; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_12);
+
+# https://www.postgresql.org/docs/11/release-11.html
+
+# max_wal_senders + superuser_reserved_connections must be < max_connections
+# uses recovery.conf
+
+sub _recovery_file { return "recovery.conf"; }
+
+sub set_standby_mode
+{
+ my $self = shift;
+ $self->append_conf(
+ "recovery.conf",
+ "standby_mode = on\n");
+}
+
+
+sub init
+{
+ my ($self, @args) = @_;
+ $self->SUPER::init(@args);
+ $self->adjust_conf('postgresql.conf','max_wal_senders','5');
+}
+
+##########################################################################
+
+package PostgresNodeV_10; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_11);
+
+# https://www.postgresql.org/docs/10/release-10.html
+
+##########################################################################
+
+package PostgresNodeV_9_6; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_10);
+
+# https://www.postgresql.org/docs/9.6/release-9-6.html
+
+# no -no-sync option for pg_basebackup
+# replication conf is a bit different too
+# lsn function names are different
+
+sub _backup_sync { return (); }
+
+sub set_replication_conf
+{
+ my ($self) = @_;
+ my $pgdata = $self->data_dir;
+
+ $self->host eq $test_pghost
+ or die "set_replication_conf only works with the default host";
+
+ open my $hba, ">>$pgdata/pg_hba.conf";
+ print $hba "\n# Allow replication (set up by PostgresNode.pm)\n";
+ if (!$TestLib::windows_os)
+ {
+ print $hba "local replication all trust\n";
+ }
+ else
+ {
+ print $hba
+"host replication all $test_localhost/32 sspi include_realm=1 map=regress\n";
+ }
+ close $hba;
+}
+
+sub _lsn_mode_map
+{
+ return (
+ 'insert' => 'pg_current_xlog_insert_location()',
+ 'flush' => 'pg_current_xlog_flush_location()',
+ 'write' => 'pg_current_xlog_location()',
+ 'receive' => 'pg_last_xlog_receive_location()',
+ 'replay' => 'pg_last_xlog_replay_location()');
+}
+
+sub _replication_suffix { return "_location"; }
+
+##########################################################################
+
+package PostgresNodeV_9_5; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_9_6);
+
+# https://www.postgresql.org/docs/9.5/release-9-5.html
+
+##########################################################################
+
+package PostgresNodeV_9_4; ## no critic (ProhibitMultiplePackages)
+
+use Test::More;
+use parent -norequire, qw(PostgresNodeV_9_5);
+
+# https://www.postgresql.org/docs/9.4/release-9-4.html
+
+# no log_replication_commands
+# no wal_retrieve_retry_interval
+# no cluster_name
+
+
+sub init
+{
+ my ($self, @args) = @_;
+ $self->SUPER::init(@args);
+ $self->adjust_conf('postgresql.conf','log_replication_commands',undef);
+ $self->adjust_conf('postgresql.conf','wal_retrieve_retry_interval',undef);
+ $self->adjust_conf('postgresql.conf','max_wal_size',undef);
+}
+
+sub _cluster_name_opt { return (); }
+
+##########################################################################
+
+package PostgresNodeV_9_3; ## no critic (ProhibitMultiplePackages)
+
+use Test::More;
+use parent -norequire, qw(PostgresNodeV_9_4);
+
+# https://www.postgresql.org/docs/9.3/release-9-3.html
+
+# no logical replication, so no logical streaming
+
+sub init
+{
+ my ($self, %params) = @_;
+ $self->SUPER::init(%params);
+ $self->adjust_conf('postgresql.conf','max_replication_slots',undef);
+ $self->adjust_conf('postgresql.conf','wal_log_hints',undef);
+ $self->adjust_conf('postgresql.conf','wal_level','hot_standby') if $params{allows_streaming};
+}
+
+sub _init_streaming
+{
+ my ($self, $conf, $allows_streaming) = @_;
+
+ print "9.3 _init_streaming called\n";
+
+ BAIL_OUT("Server Version too old for logical replication")
+ if ($allows_streaming eq "logical");
+ $self->SUPER::_init_streaming($conf, $allows_streaming);
+}
+
+
+##########################################################################
+
+package PostgresNodeV_9_2; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_9_3);
+
+# https://www.postgresql.org/docs/9.3/release-9-2.html
+
+# no -N flag to initdb
+# socket location is in unix_socket_directory
+
+sub _initdb_flags { return ('-A', 'trust'); }
+
+sub _init_network
+{
+ my ($self, $conf, $use_tcp, $host) = @_;
+
+ if ($use_tcp)
+ {
+ print $conf "unix_socket_directory = ''\n";
+ print $conf "listen_addresses = '$host'\n";
+ }
+ else
+ {
+ print $conf "unix_socket_directory = '$host'\n";
+ print $conf "listen_addresses = ''\n";
+ }
+}
+
+sub _init_network_append
+{
+ my ($self, $use_tcp, $host) = @_;
+
+ if ($use_tcp)
+ {
+ $self->append_conf('postgresql.conf', "listen_addresses = '$host'");
+ }
+ else
+ {
+ $self->append_conf('postgresql.conf',
+ "unix_socket_directory = '$host'");
+ }
+}
+
+
+##########################################################################
+
+package PostgresNodeV_9_1; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_9_2);
+
+# https://www.postgresql.org/docs/9.3/release-9-1.html
+
+##########################################################################
+
+package PostgresNodeV_9_0; ## no critic (ProhibitMultiplePackages)
+
+use Test::More;
+use parent -norequire, qw(PostgresNodeV_9_1);
+
+# https://www.postgresql.org/docs/9.3/release-9-0.html
+
+# no wal_senders setting
+# no pg_basebackup
+# can't turn off restart after crash
+
+sub init
+{
+ my ($self, @args) = @_;
+ $self->SUPER::init(@args);
+ $self->adjust_conf('postgresql.conf','restart_after_crash',undef);
+ $self->adjust_conf('postgresql.conf','wal_senders',undef);
+}
+
+sub _init_restart_after_crash { return ""; }
+
+sub backup
+{
+ BAIL_OUT("Server version too old for backup function");
+}
+
+sub init_from_backup
+{
+ BAIL_OUT("Server version too old for init_from_backup function");
+}
+
+sub _init_streaming
+{
+ my ($self, $conf, $allows_streaming) = @_;
+
+ BAIL_OUT("Server Version too old for logical replication")
+ if ($allows_streaming eq "logical");
+ $self->SUPER::_init_streaming($conf, $allows_streaming);
+}
+
+##########################################################################
+
+package PostgresNodeV_8_4; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_9_0);
+
+# https://www.postgresql.org/docs/9.3/release-8-4.html
+
+# no wal_level setting
+
+sub _init_wal_level_minimal
+{
+ # do nothing
+}
+
+
+sub _init_restart_after_crash { return ""; }
+
+##########################################################################
+
+package PostgresNodeV_8_3; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_8_4);
+
+# https://www.postgresql.org/docs/9.3/release-8-3.html
+
+# no stats_temp_directory setting
+# no -w flag for psql
+
+sub init
+{
+ my ($self, @args) = @_;
+ $self->SUPER::init(@args);
+ $self->adjust_conf('postgresql.conf','stats_temp_directory',undef);
+}
+
+sub psql
+{
+ my ($self, $dbname, $sql, %params) = @_;
+
+ local $ENV{PGPASSWORD};
+
+ if ($params{no_password})
+ {
+ # since there is no -w flag for psql here, we try to
+ # inhibit a password prompt by setting PGPASSWORD instead
+ $ENV{PGPASSWORD} = 'no_such_password_12345';
+ delete $params{no_password};
+ }
+
+ $self->SUPER::psql($dbname, $sql, %params);
+}
+
+##########################################################################
+
+package PostgresNodeV_8_2; ## no critic (ProhibitMultiplePackages)
+
+use Test::More;
+use parent -norequire, qw(PostgresNodeV_8_3);
+
+
+# https://www.postgresql.org/docs/9.3/release-8-2.html
+
+# no support for connstr with =
+
+sub psql
+{
+ my ($self, $dbname, $sql, %params) = @_;
+
+ my $connstr = $params{connstr};
+
+ BAIL_OUT("Server version too old: complex connstr with = not supported")
+ if (defined($connstr) && $connstr =~ /=/);
+
+ # Handle the simple common case where there's no explicit connstr
+ $params{host} ||= $self->host;
+ $params{port} ||= $self->port;
+ # Supply this so the superclass doesn't try to construct a connstr
+ $params{connstr} ||= $dbname;
+
+ $self->SUPER::psql($dbname, $sql, %params);
+}
+
+##########################################################################
+
+package PostgresNodeV_8_1; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_8_2);
+
+# https://www.postgresql.org/docs/9.3/release-8-1.html
+
+##########################################################################
+
+package PostgresNodeV_8_0; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_8_1);
+
+# https://www.postgresql.org/docs/9.3/release-8-0.html
+
+##########################################################################
+
+package PostgresNodeV_7_4; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_8_0);
+
+# https://www.postgresql.org/docs/9.3/release-7-4.html
+
+# no '-A trust' for initdb
+# no log_line_prefix
+# no 'log_statement = all' (only 'on')
+# no listen_addresses - use tcpip_socket and virtual_host instead
+# no archiving
+
+sub _initdb_flags { return (); }
+
+sub init
+{
+ my ($self, @args) = @_;
+ $self->SUPER::init(@args);
+ $self->adjust_conf('postgresql.conf','log_line_prefix',undef);
+ $self->adjust_conf('postgresql.conf','log_statement','on');
+}
+
+sub _init_network
+{
+ my ($self, $conf, $use_tcp, $host) = @_;
+
+ if ($use_tcp)
+ {
+ print $conf "unix_socket_directory = ''\n";
+ print $conf "virtual_host = '$host'\n";
+ print $conf "tcpip_socket = true\n";
+ }
+ else
+ {
+ print $conf "unix_socket_directory = '$host'\n";
+ }
+}
+
+
+##########################################################################
+
+package PostgresNodeV_7_3; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_7_4);
+
+# https://www.postgresql.org/docs/9.3/release-7-3.html
+
+##########################################################################
+
+package PostgresNodeV_7_2; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_7_3);
+
+# https://www.postgresql.org/docs/9.3/release-7-2.html
+
+# no log_statement
+
+sub init
+{
+ my ($self, @args) = @_;
+ $self->SUPER::init(@args);
+ $self->adjust_conf('postgresql.conf','log_statement',undef);
+}
+
+##########################################################################
+# traditional module 'value'
+
1;
diff --git a/src/test/perl/PostgresVersion.pm b/src/test/perl/PostgresVersion.pm
new file mode 100644
index 0000000000..d99c991597
--- /dev/null
+++ b/src/test/perl/PostgresVersion.pm
@@ -0,0 +1,61 @@
+
+package PostgresVersion;
+
+use strict;
+use warnings;
+
+use Scalar::Util qw(blessed);
+
+use overload
+ '<=>' => \&_version_cmp,
+ 'cmp' => \&_version_cmp;
+
+sub new
+{
+ my $class = shift;
+ my $arg = shift;
+
+ # Accept standard formats, in case caller has handed us the output of a
+ # postgres command line tool
+ $arg = $1
+ if ($arg =~ m/\(?PostgreSQL\)? (\d+(?:\.\d+)*(?:devel)?)/);
+
+ # Split into an array
+ my @result = split(/\./, $arg);
+
+ # Treat development versions as having a minor/micro version one less than
+ # the first released version of that branch.
+ if ($result[$#result] =~ m/^(\d+)devel$/)
+ {
+ pop(@result);
+ push(@result, $1, -1);
+ }
+
+ my $res = [ @result ];
+ bless $res, $class;
+ return $res;
+}
+
+
+# Routine which compares the _pg_version_array obtained for the two
+# arguments and returns -1, 0, or 1, allowing comparison between two
+# PostgresNodes or a PostgresNode and a version string.
+#
+# if the second argument is not a blessed object ew call the constructor
+# to make one.
+#
+sub _version_cmp
+{
+ my ($a, $b) = @_;
+
+ $b = __PACKAGE__->new($b) unless blessed($b);
+
+ for (my $idx = 0; ; $idx++)
+ {
+ return 0 unless (defined $a->[$idx] && defined $b->[$idx]);
+ return $a->[$idx] <=> $b->[$idx]
+ if ($a->[$idx] <=> $b->[$idx]);
+ }
+}
+
+1;