diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index e26b2b3f30..e48379e3fd 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -127,6 +127,11 @@ 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;
+
 =pod
 
 =head1 METHODS
@@ -347,9 +352,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";
@@ -797,7 +805,7 @@ 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',
+	$ret = TestLib::system_log('pg_ctl', '-w', '-D', $self->data_dir, '-l',
 		$self->logfile, '-o', "--cluster-name=$name", 'start');
 
 	if ($ret != 0)
@@ -911,7 +919,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);
@@ -1196,9 +1204,188 @@ 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}->[0] <= $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} = _pg_version_array($version_line);
+
+	BAIL_OUT("could not parse pg_config --version output: $version_line")
+		unless defined $self->{_pg_version};
+}
+
+# Private routine which returns a reference to an array of integers
+# representing the pg_version of a PostgresNode, or parsed from a postgres
+# version string.  Development versions (such as "14devel") are converted
+# to an array with minus one as the last value (such as [14, -1]).
+#
+# For idempotency, will return the argument back to the caller if handed an
+# array reference.
+sub _pg_version_array
+{
+	my ($arg) = @_;
+
+	# accept node arguments
+	return _pg_version_array($arg->{_pg_version})
+		if (blessed($arg) && $arg->isa("PostgresNode"));
+
+	# idempotency
+	return $arg
+		if (ref($arg) && ref($arg) =~ /ARRAY/);
+
+	# 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);
+	}
+
+	# Return an array reference
+	return \@result;
+}
+
+# Private 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.
+#
+# To achieve intuitive behavior when comparing a PostgresNode against a version
+# string, "X" is equal to "X.Y" for any value of Y.  This allows calls like
+#
+#    $node->newer_than_version("14")
+#
+# to return true starting with "15devel", but false for "14devel", "14.0",
+# "14.1", etc.  It also allows
+#
+#    $node->at_least_version("14")
+#
+# to work for a node of version "14devel", where comparing against "14.0" would
+# fail.
+#
+sub _pg_version_cmp
+{
+	my ($a, $b) = @_;
+
+	$a = _pg_version_array($a);
+	$b = _pg_version_array($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]);
+	}
+}
+
+=pod
+
+=item $node->older_than_version(other)
+
+Returns whether this node's postgres version is older than "other", which can be
+either another PostgresNode or a version string.
+
+=cut
+
+sub older_than_version
+{
+	my ($node, $pg_version) = @_;
+	return _pg_version_cmp($node->{_pg_version}, $pg_version) < 0;
+}
+
+=pod
+
+=item $node->newer_than_version(other)
+
+Returns whether this node's postgres version is newer than "other", which can
+be either another PostgresNode or a version string.
+
+=cut
+
+sub newer_than_version
+{
+	my ($node, $pg_version) = @_;
+	return _pg_version_cmp($node->{_pg_version}, $pg_version) > 0;
+}
+
+=pod
+
+=item $node->at_least_version(other)
+
+Returns whether this node's postgres version is at least as new as "other",
+which can be either another PostgresNode or a version string.
+
+=cut
+
+sub at_least_version
+{
+	my ($node, $pg_version) = @_;
+	return _pg_version_cmp($node->{_pg_version}, $pg_version) >= 0;
+}
+
 # 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 +1458,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 +1725,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 +1785,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 +1987,7 @@ sub background_psql
 	my $replication = $params{replication};
 
 	my @psql_params = (
-		'psql',
+		$self->installed_command('psql'),
 		'-XAtq',
 		'-d',
 		$self->connstr($dbname)
@@ -1831,7 +2064,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 +2125,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
@@ -1938,7 +2183,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);
 
@@ -2067,7 +2314,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;
@@ -2489,7 +2742,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';
@@ -2556,4 +2810,738 @@ sub pg_recvlogical_upto
 
 =cut
 
+package PostgresNodeV_13; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNode);
+
+package PostgresNodeV_12; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_13);
+
+package PostgresNodeV_11; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_12);
+
+package PostgresNodeV_10; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_11);
+
+package PostgresNodeV_9_6; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_10);
+
+package PostgresNodeV_9_5; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_9_6);
+
+package PostgresNodeV_9_4; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_9_5);
+
+sub init
+{
+	my ($self, %params) = @_;
+	my $port   = $self->port;
+	my $pgdata = $self->data_dir;
+	my $host   = $self->host;
+
+	local %ENV = $self->_get_env();
+
+	$params{allows_streaming} = 0 unless defined $params{allows_streaming};
+	$params{has_archiving}    = 0 unless defined $params{has_archiving};
+
+	mkdir $self->backup_dir;
+	mkdir $self->archive_dir;
+
+	my @initdb_nosync_opt = ('-N');
+
+	my @trust_opt = ('-A', 'trust');
+
+	TestLib::system_or_bail('initdb', '-D', $pgdata,
+		@trust_opt, @initdb_nosync_opt,
+		@{ $params{extra} });
+	TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata,
+		@{ $params{auth_extra} });
+
+	open my $conf, '>>', "$pgdata/postgresql.conf";
+	print $conf "\n# Added by PostgresNode.pm\n";
+	print $conf "fsync = off\n";
+
+	print $conf "restart_after_crash = off\n";
+	print $conf "log_line_prefix = '%m [%p] %q%a '\n";
+	print $conf "log_statement = all\n";
+
+	# If a setting tends to affect whether tests pass or fail, print it after
+	# TEMP_CONFIG.  Otherwise, print it before TEMP_CONFIG, thereby permitting
+	# overrides.  Settings that merely improve performance or ease debugging
+	# belong before TEMP_CONFIG.
+	print $conf TestLib::slurp_file($ENV{TEMP_CONFIG})
+	  if defined $ENV{TEMP_CONFIG};
+
+	# XXX Neutralize any stats_temp_directory in TEMP_CONFIG.  Nodes running
+	# concurrently must not share a stats_temp_directory.
+	print $conf "stats_temp_directory = 'pg_stat_tmp'\n";
+
+	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";
+	}
+	else
+	{
+		print $conf "wal_level = minimal\n";
+		print $conf "max_wal_senders = 0\n";
+	}
+
+	print $conf "port = $port\n";
+
+	if ($use_tcp)
+	{
+		print $conf "unix_socket_directories = ''\n";
+		print $conf "listen_addresses = '$host'\n";
+	}
+	else
+	{
+		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");
+
+	$self->set_replication_conf if $params{allows_streaming};
+	$self->enable_archiving     if $params{has_archiving};
+	return;
+}
+
+sub start
+{
+	my ($self, %params) = @_;
+	my $port   = $self->port;
+	my $pgdata = $self->data_dir;
+	my $name   = $self->name;
+	my $ret;
+
+	BAIL_OUT("node \"$name\" is already running") if defined $self->{_pid};
+
+	print("### Starting node \"$name\"\n");
+
+	# Temporarily unset PGAPPNAME so that the server doesn't
+	# inherit it.  Otherwise this could affect libpqwalreceiver
+	# connections in confusing ways.
+	local %ENV = $self->_get_env(PGAPPNAME => undef);
+
+	$ret = TestLib::system_log('pg_ctl', '-w', '-D', $self->data_dir, '-l',
+		$self->logfile,
+		'start');
+
+	if ($ret != 0)
+	{
+		print "# pg_ctl start failed; logfile:\n";
+		print TestLib::slurp_file($self->logfile);
+		BAIL_OUT("pg_ctl start failed") unless $params{fail_ok};
+		return 0;
+	}
+
+	$self->_update_pid(1);
+	return 1;
+}
+
+package PostgresNodeV_9_3; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_9_4);
+
+sub init
+{
+	my ($self, %params) = @_;
+	my $port   = $self->port;
+	my $pgdata = $self->data_dir;
+	my $host   = $self->host;
+
+	local %ENV = $self->_get_env();
+
+	# Check version support for requested features
+	BAIL_OUT("Server version too old: 'allows_streaming' option not supported");
+
+	$params{has_archiving}    = 0 unless defined $params{has_archiving};
+
+	mkdir $self->backup_dir;
+	mkdir $self->archive_dir;
+
+	# initdb option -N/--no-sync was added in version 9.3.  If the server
+	# version is new enough, we can use this option to make the tests run
+	# faster by not waiting for the disks to sync.
+	my @initdb_nosync_opt = ('-N');
+
+	my @trust_opt = ('-A', 'trust');
+
+	TestLib::system_or_bail('initdb', '-D', $pgdata,
+		@trust_opt, @initdb_nosync_opt,
+		@{ $params{extra} });
+	TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata,
+		@{ $params{auth_extra} });
+
+	open my $conf, '>>', "$pgdata/postgresql.conf";
+	print $conf "\n# Added by PostgresNode.pm\n";
+	print $conf "fsync = off\n";
+
+	# "restart_after_crash" was introduced in version 9.1.  Older versions
+	# always restart after crash.
+	print $conf "restart_after_crash = off\n";
+	print $conf "log_line_prefix = '%m [%p] %q%a '\n";
+	print $conf "log_statement = all\n";
+
+	# If a setting tends to affect whether tests pass or fail, print it after
+	# TEMP_CONFIG.  Otherwise, print it before TEMP_CONFIG, thereby permitting
+	# overrides.  Settings that merely improve performance or ease debugging
+	# belong before TEMP_CONFIG.
+	print $conf TestLib::slurp_file($ENV{TEMP_CONFIG})
+	  if defined $ENV{TEMP_CONFIG};
+
+	# XXX Neutralize any stats_temp_directory in TEMP_CONFIG.  Nodes running
+	# concurrently must not share a stats_temp_directory.
+	print $conf "stats_temp_directory = 'pg_stat_tmp'\n";
+
+	print $conf "wal_level = minimal\n";
+	print $conf "max_wal_senders = 0\n";
+
+	print $conf "port = $port\n";
+
+	if ($use_tcp)
+	{
+		print $conf "unix_socket_directories = ''\n";
+		print $conf "listen_addresses = '$host'\n";
+	}
+	else
+	{
+		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");
+
+	$self->enable_archiving     if $params{has_archiving};
+	return;
+}
+
+package PostgresNodeV_9_2; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_9_3);
+
+sub init
+{
+	my ($self, %params) = @_;
+	my $port   = $self->port;
+	my $pgdata = $self->data_dir;
+	my $host   = $self->host;
+
+	local %ENV = $self->_get_env();
+
+	# Check version support for requested features
+	BAIL_OUT("Server version too old: 'allows_streaming' option not supported")
+		if ($params{allows_streaming});
+
+	$params{has_archiving}    = 0 unless defined $params{has_archiving};
+
+	mkdir $self->backup_dir;
+	mkdir $self->archive_dir;
+
+	my @trust_opt = ('-A', 'trust');
+
+	TestLib::system_or_bail('initdb', '-D', $pgdata,
+		@trust_opt,
+		@{ $params{extra} });
+	TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata,
+		@{ $params{auth_extra} });
+
+	open my $conf, '>>', "$pgdata/postgresql.conf";
+	print $conf "\n# Added by PostgresNode.pm\n";
+	print $conf "fsync = off\n";
+
+	print $conf "restart_after_crash = off\n";
+	print $conf "log_line_prefix = '%m [%p] %q%a '\n";
+	print $conf "log_statement = all\n";
+
+	# If a setting tends to affect whether tests pass or fail, print it after
+	# TEMP_CONFIG.  Otherwise, print it before TEMP_CONFIG, thereby permitting
+	# overrides.  Settings that merely improve performance or ease debugging
+	# belong before TEMP_CONFIG.
+	print $conf TestLib::slurp_file($ENV{TEMP_CONFIG})
+	  if defined $ENV{TEMP_CONFIG};
+
+	# XXX Neutralize any stats_temp_directory in TEMP_CONFIG.  Nodes running
+	# concurrently must not share a stats_temp_directory.
+	print $conf "stats_temp_directory = 'pg_stat_tmp'\n";
+
+	print $conf "wal_level = minimal\n";
+	print $conf "max_wal_senders = 0\n";
+
+	print $conf "port = $port\n";
+
+	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";
+	}
+	close $conf;
+
+	chmod($self->group_access ? 0640 : 0600, "$pgdata/postgresql.conf")
+	  or die("unable to set permissions for $pgdata/postgresql.conf");
+
+	$self->enable_archiving     if $params{has_archiving};
+	return;
+}
+
+sub init_from_backup
+{
+	my ($self, $root_node, $backup_name, %params) = @_;
+	my $backup_path = $root_node->backup_dir . '/' . $backup_name;
+	my $host        = $self->host;
+	my $port        = $self->port;
+	my $node_name   = $self->name;
+	my $root_name   = $root_node->name;
+
+	BAIL_OUT("Server version too old: 'has_streaming' option not supported")
+		if ($params{has_streaming});
+
+	$params{has_restoring} = 0 unless defined $params{has_restoring};
+	$params{standby}       = 1 unless defined $params{standby};
+
+	print
+	  "# Initializing node \"$node_name\" from backup \"$backup_name\" of node \"$root_name\"\n";
+#	croak "Backup \"$backup_name\" does not exist at $backup_path"
+#	  unless -d $backup_path;
+
+	mkdir $self->backup_dir;
+	mkdir $self->archive_dir;
+
+	my $data_path = $self->data_dir;
+	if (defined $params{tar_program})
+	{
+		mkdir($data_path);
+		TestLib::system_or_bail($params{tar_program}, 'xf',
+			$backup_path . '/base.tar',
+			'-C', $data_path);
+		TestLib::system_or_bail($params{tar_program}, 'xf',
+			$backup_path . '/pg_wal.tar',
+			'-C', $data_path . '/pg_wal');
+	}
+	else
+	{
+		rmdir($data_path);
+		RecursiveCopy::copypath($backup_path, $data_path);
+	}
+	chmod(0700, $data_path);
+
+	# Base configuration for this node
+	$self->append_conf(
+		'postgresql.conf',
+		qq(
+port = $port
+));
+	if ($use_tcp)
+	{
+		$self->append_conf('postgresql.conf', "listen_addresses = '$host'");
+	}
+	else
+	{
+		$self->append_conf('postgresql.conf',
+						   "unix_socket_directory = '$host'");
+	}
+	$self->enable_restoring($root_node, $params{standby})
+	  if $params{has_restoring};
+	return;
+}
+
+package PostgresNodeV_9_1; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_9_2);
+
+package PostgresNodeV_9_0; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_9_1);
+
+sub init
+{
+	my ($self, %params) = @_;
+	my $port   = $self->port;
+	my $pgdata = $self->data_dir;
+	my $host   = $self->host;
+
+	local %ENV = $self->_get_env();
+
+	# Check version support for requested features
+	BAIL_OUT("Server version too old: 'allows_streaming' option not supported")
+		if ($params{allows_streaming});
+
+	$params{has_archiving}    = 0 unless defined $params{has_archiving};
+
+	mkdir $self->backup_dir;
+	mkdir $self->archive_dir;
+
+	my @trust_opt = ('-A', 'trust');
+
+	TestLib::system_or_bail('initdb', '-D', $pgdata,
+		@trust_opt,
+		@{ $params{extra} });
+	TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata,
+		@{ $params{auth_extra} });
+
+	open my $conf, '>>', "$pgdata/postgresql.conf";
+	print $conf "\n# Added by PostgresNode.pm\n";
+	print $conf "fsync = off\n";
+
+	print $conf "log_line_prefix = '%m [%p] %q%a '\n";
+	print $conf "log_statement = all\n";
+
+	# If a setting tends to affect whether tests pass or fail, print it after
+	# TEMP_CONFIG.  Otherwise, print it before TEMP_CONFIG, thereby permitting
+	# overrides.  Settings that merely improve performance or ease debugging
+	# belong before TEMP_CONFIG.
+	print $conf TestLib::slurp_file($ENV{TEMP_CONFIG})
+	  if defined $ENV{TEMP_CONFIG};
+
+	# XXX Neutralize any stats_temp_directory in TEMP_CONFIG.  Nodes running
+	# concurrently must not share a stats_temp_directory.
+	print $conf "stats_temp_directory = 'pg_stat_tmp'\n";
+
+	print $conf "wal_level = minimal\n";
+	print $conf "max_wal_senders = 0\n";
+
+	print $conf "port = $port\n";
+
+	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";
+	}
+	close $conf;
+
+	chmod($self->group_access ? 0640 : 0600, "$pgdata/postgresql.conf")
+	  or die("unable to set permissions for $pgdata/postgresql.conf");
+
+	$self->enable_archiving     if $params{has_archiving};
+	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");
+}
+
+package PostgresNodeV_8_4; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_9_0);
+
+sub init
+{
+	my ($self, %params) = @_;
+	my $port   = $self->port;
+	my $pgdata = $self->data_dir;
+	my $host   = $self->host;
+
+	local %ENV = $self->_get_env();
+
+	# Check version support for requested features
+	BAIL_OUT("Server version too old: 'allows_streaming' option not supported")
+		if ($params{allows_streaming});
+
+	$params{has_archiving}    = 0 unless defined $params{has_archiving};
+
+	mkdir $self->backup_dir;
+	mkdir $self->archive_dir;
+
+	my @trust_opt = ('-A', 'trust');
+
+	TestLib::system_or_bail('initdb', '-D', $pgdata,
+		@trust_opt,
+		@{ $params{extra} });
+	TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata,
+		@{ $params{auth_extra} });
+
+	open my $conf, '>>', "$pgdata/postgresql.conf";
+	print $conf "\n# Added by PostgresNode.pm\n";
+	print $conf "fsync = off\n";
+
+	print $conf "log_line_prefix = '%m [%p] %q%a '\n";
+	print $conf "log_statement = all\n";
+
+	# If a setting tends to affect whether tests pass or fail, print it after
+	# TEMP_CONFIG.  Otherwise, print it before TEMP_CONFIG, thereby permitting
+	# overrides.  Settings that merely improve performance or ease debugging
+	# belong before TEMP_CONFIG.
+	print $conf TestLib::slurp_file($ENV{TEMP_CONFIG})
+	  if defined $ENV{TEMP_CONFIG};
+
+	# XXX Neutralize any stats_temp_directory in TEMP_CONFIG.  Nodes running
+	# concurrently must not share a stats_temp_directory.
+	print $conf "stats_temp_directory = 'pg_stat_tmp'\n";
+
+	print $conf "port = $port\n";
+
+	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";
+	}
+	close $conf;
+
+	chmod($self->group_access ? 0640 : 0600, "$pgdata/postgresql.conf")
+	  or die("unable to set permissions for $pgdata/postgresql.conf");
+
+	$self->enable_archiving     if $params{has_archiving};
+	return;
+}
+
+package PostgresNodeV_8_3; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_8_4);
+
+sub psql
+{
+	my ($self, $dbname, $sql, %params) = @_;
+
+	BAIL_OUT("Node version too old: 'no_password' option not supported")
+	  if ($params{no_password});
+
+	$self->SUPER::psql($dbname, $sql, %params);
+
+}
+
+sub init
+{
+	my ($self, %params) = @_;
+	my $port   = $self->port;
+	my $pgdata = $self->data_dir;
+	my $host   = $self->host;
+
+	local %ENV = $self->_get_env();
+
+	# Check version support for requested features
+	BAIL_OUT("Server version too old: 'allows_streaming' option not supported")
+		if ($params{allows_streaming});
+
+	$params{has_archiving}    = 0 unless defined $params{has_archiving};
+
+	mkdir $self->backup_dir;
+	mkdir $self->archive_dir;
+
+	my @trust_opt = ('-A', 'trust');
+
+	TestLib::system_or_bail('initdb', '-D', $pgdata,
+		@trust_opt,
+		@{ $params{extra} });
+	TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata,
+		@{ $params{auth_extra} });
+
+	open my $conf, '>>', "$pgdata/postgresql.conf";
+	print $conf "\n# Added by PostgresNode.pm\n";
+	print $conf "fsync = off\n";
+
+	print $conf "log_line_prefix = '%m [%p] %q%a '\n";
+	print $conf "log_statement = all\n";
+
+	# If a setting tends to affect whether tests pass or fail, print it after
+	# TEMP_CONFIG.  Otherwise, print it before TEMP_CONFIG, thereby permitting
+	# overrides.  Settings that merely improve performance or ease debugging
+	# belong before TEMP_CONFIG.
+	print $conf TestLib::slurp_file($ENV{TEMP_CONFIG})
+	  if defined $ENV{TEMP_CONFIG};
+
+	print $conf "port = $port\n";
+
+	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";
+	}
+	close $conf;
+
+	chmod($self->group_access ? 0640 : 0600, "$pgdata/postgresql.conf")
+	  or die("unable to set permissions for $pgdata/postgresql.conf");
+
+	$self->enable_archiving     if $params{has_archiving};
+	return;
+}
+
+package PostgresNodeV_8_2; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_8_3);
+
+package PostgresNodeV_8_1; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_8_2);
+
+package PostgresNodeV_8_0; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_8_1);
+
+package PostgresNodeV_7_4; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_8_0);
+
+sub init
+{
+	my ($self, %params) = @_;
+	my $port   = $self->port;
+	my $pgdata = $self->data_dir;
+	my $host   = $self->host;
+
+	local %ENV = $self->_get_env();
+
+	# Check version support for requested features
+	BAIL_OUT("Server version too old: 'allows_streaming' option not supported")
+		if ($params{allows_streaming});
+
+	BAIL_OUT("Server version too old: 'has_archiving' option not supported")
+		if ($params{has_archiving});
+
+	mkdir $self->backup_dir;
+
+	TestLib::system_or_bail('initdb', '-D', $pgdata,
+		@{ $params{extra} });
+	TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata,
+		@{ $params{auth_extra} });
+
+	open my $conf, '>>', "$pgdata/postgresql.conf";
+	print $conf "\n# Added by PostgresNode.pm\n";
+	print $conf "fsync = off\n";
+
+	print $conf "log_statement = on\n";
+
+	# If a setting tends to affect whether tests pass or fail, print it after
+	# TEMP_CONFIG.  Otherwise, print it before TEMP_CONFIG, thereby permitting
+	# overrides.  Settings that merely improve performance or ease debugging
+	# belong before TEMP_CONFIG.
+	print $conf TestLib::slurp_file($ENV{TEMP_CONFIG})
+	  if defined $ENV{TEMP_CONFIG};
+
+	print $conf "port = $port\n";
+
+	if ($use_tcp)
+	{
+		print $conf "unix_socket_directory = ''\n";
+		print $conf "tcpip_socket = true\n";
+		print $conf "virtual_host = '$host'\n";
+	}
+	else
+	{
+		print $conf "unix_socket_directory = '$host'\n";
+		print $conf "tcpip_socket = false\n";
+	}
+	close $conf;
+
+	chmod($self->group_access ? 0640 : 0600, "$pgdata/postgresql.conf")
+	  or die("unable to set permissions for $pgdata/postgresql.conf");
+
+	return;
+}
+
+package PostgresNodeV_7_3; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire, qw(PostgresNodeV_7_4);
+
+package PostgresNodeV_7_2; ## no critic (ProhibitMultiplePackages)
+
+use parent -norequire,  qw(PostgresNodeV_7_3);
+
+sub init
+{
+	my ($self, %params) = @_;
+	my $port   = $self->port;
+	my $pgdata = $self->data_dir;
+	my $host   = $self->host;
+
+	local %ENV = $self->_get_env();
+
+	# Check version support for requested features
+	BAIL_OUT("Server version too old: 'allows_streaming' option not supported")
+		if ($params{allows_streaming});
+
+	BAIL_OUT("Server version too old: 'has_archiving' option not supported")
+		if ($params{has_archiving});
+
+	mkdir $self->backup_dir;
+
+	TestLib::system_or_bail('initdb', '-D', $pgdata,
+		@{ $params{extra} });
+	TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata,
+		@{ $params{auth_extra} });
+
+	open my $conf, '>>', "$pgdata/postgresql.conf";
+	print $conf "\n# Added by PostgresNode.pm\n";
+	print $conf "fsync = off\n";
+
+	# If a setting tends to affect whether tests pass or fail, print it after
+	# TEMP_CONFIG.  Otherwise, print it before TEMP_CONFIG, thereby permitting
+	# overrides.  Settings that merely improve performance or ease debugging
+	# belong before TEMP_CONFIG.
+	print $conf TestLib::slurp_file($ENV{TEMP_CONFIG})
+	  if defined $ENV{TEMP_CONFIG};
+
+	print $conf "port = $port\n";
+
+	if ($use_tcp)
+	{
+		print $conf "unix_socket_directory = ''\n";
+		print $conf "tcpip_socket = true\n";
+		print $conf "virtual_host = '$host'\n";
+	}
+	else
+	{
+		print $conf "unix_socket_directory = '$host'\n";
+		print $conf "tcpip_socket = false\n";
+	}
+	close $conf;
+
+	chmod($self->group_access ? 0640 : 0600, "$pgdata/postgresql.conf")
+	  or die("unable to set permissions for $pgdata/postgresql.conf");
+
+	return;
+}
+
+
 1;
