On 4/12/21 10:57 AM, Jehan-Guillaume de Rorthais wrote: > On Mon, 12 Apr 2021 09:52:24 -0400 > Andrew Dunstan <and...@dunslane.net> wrote: > >> On 4/12/21 8:59 AM, Jehan-Guillaume de Rorthais wrote: >>> Hi, >>> >>> On Wed, 7 Apr 2021 20:07:41 +0200 >>> Jehan-Guillaume de Rorthais <j...@dalibo.com> wrote: >>> [...] >>>>>> Let me know if it worth that I work on an official patch. >>>>> Let's give it a try ... >>>> OK >>> So, as promised, here is my take to port my previous work on PostgreSQL >>> source tree. >>> >>> Make check pass with no errors. The new class probably deserve some own TAP >>> tests. >>> >>> Note that I added a PostgresVersion class for easier and cleaner version >>> comparisons. This could be an interesting take away no matter what. >>> >>> I still have some more ideas to cleanup, revamp and extend the base class, >>> but I prefer to go incremental to keep things review-ability. >>> >> Thanks for this. We have been working somewhat on parallel lines. With >> your permission I'm going to take some of what you've done and >> incorporate it in the patch I'm working on. > The current context makes my weeks difficult to plan and quite chaotic, that's > why it took me some days to produce the patch I promised. > > I'm fine with working with a common base code, thanks. Feel free to merge both > code, we'll trade patches during review. However, I'm not sure what is the > status of your patch, so I can not judge what would be the easier way to > incorporate. Mine is tested down to 9.1 (9.0 was meaningless because of lack > of > pg_stat_replication) with: > > * get_new_node > * init(allows_streaming => 1) > * start > * stop > * backup > * init_from_backup > * wait_for_catchup > * command_checks_all > > Note the various changes in my init() and new method allow_streaming(), etc. > > FYI (to avoid duplicate work), the next step on my todo was to produce some > meaningful tap tests to prove the class. > >> A PostgresVersion class is a good idea - I was already contemplating >> something of the kind. > Thanks! >
OK, here is more WIP on this item. I have drawn substantially on Mark's and Jehan-Guillaime's work, but it doesn't really resemble either, and I take full responsibility for it. The guiding principles have been: . avoid doing version tests, or capability tests which are the moral equivalent, and rely instead on pure overloading. . avoid overriding large pieces of code. The last has involved breaking up some large subroutines (like init) into pieces which can more sensibly be overridden. Breaking them up isn't a bad thing to do anyway. There is a new PostgresVersion object, but it's deliberately very minimal. Since we're not doing version tests we don't need more complex routines. 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..1d0866eea7 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} }); @@ -446,11 +464,11 @@ sub init 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"; - print $conf "log_replication_commands = on\n"; - print $conf "wal_retrieve_retry_interval = '500ms'\n"; + print $conf $self->_init_restart_after_crash; + print $conf $self->_init_log_line_prefix; + print $conf $self->_init_log_statement; + print $conf $self->_init_log_replication_commands; + print $conf $self->_init_wal_retrieve_retry_interval; # If a setting tends to affect whether tests pass or fail, print it after # TEMP_CONFIG. Otherwise, print it before TEMP_CONFIG, thereby permitting @@ -461,35 +479,47 @@ sub init # 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 $self->_init_stats_temp_directory; + 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 _init_restart_after_crash { return "restart_after_crash = off\n"; } +sub _init_log_line_prefix { return "log_line_prefix = '%m [%p] %q%a '\n"; } +sub _init_log_statement { return "log_statement = all\n"; } +sub _init_log_replication_commands { return "log_replication_commands = on\n"; } +sub _init_wal_retrieve_retry_interval { return "wal_retrieve_retry_interval = '500ms'\n"; } +sub _init_stats_temp_directory { "stats_temp_directory = 'pg_stat_tmp'\n"; } + +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 +530,42 @@ 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 $self->_init_streaming_wal_level; + } + print $conf $self->_init_max_wal_senders; + print $conf $self->_init_max_replication_slots; + print $conf $self->_init_wal_log_hints; + 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 $self->_init_max_wal_size; +} + +sub _init_max_wal_senders { return "max_wal_senders = 10\n"; } +sub _init_max_replication_slots { return "max_replication_slots = 10\n"; } +sub _init_streaming_wal_level { return "wal_level = 'replica'\n"; } +sub _init_wal_log_hints { return "wal_log_hints = on\n"; } +sub _init_max_wal_size { return "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 @@ -565,12 +623,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 +785,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 +806,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 +865,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 +880,12 @@ sub start return 1; } +sub _cluster_name_opt +{ + my ($self, $name) = @_; + return ('-o', "--cluster-name=$name"); +} + =pod =item $node->kill9() @@ -911,7 +985,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 +1049,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 +1080,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 +1272,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 +1410,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 +1677,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 +1737,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 +1939,7 @@ sub background_psql my $replication = $params{replication}; my @psql_params = ( - 'psql', + $self->installed_command('psql'), '-XAtq', '-d', $self->connstr($dbname) @@ -1831,7 +2016,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 +2077,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 +2121,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 +2240,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 +2430,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 +2448,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 +2504,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 +2515,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 +2675,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 +2743,420 @@ 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_max_wal_senders { return "max_wal_senders = 5\n"; } + + ########################################################################## + +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_log_replication_commands { return ""; } +sub _init_wal_retrieve_retry_interval { return ""; } + +sub _cluster_name_opt { return (); } + +sub _init_max_wal_size { 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_max_replication_slots { return ""; } +sub _init_wal_log_hints { return ""; } +sub _init_streaming_wal_level { return "wal_level = 'hot_standby'\n"; } + +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_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_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_wal_level_minimal +{ + my ($self, $conf) = @_; + print $conf "wal_level = minimal\n"; +} + +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_stats_temp_directory { return ""; } + +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_log_line_prefix { return ""; } +sub _init_log_statement { return "log_statement = on \n"; } + +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_log_statement { return ""; } + +########################################################################## +# traditional module 'value' + 1; diff --git a/src/test/perl/PostgresVersion.pm b/src/test/perl/PostgresVersion.pm new file mode 100644 index 0000000000..c51be162df --- /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;