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,
>From 077e447995b3b8bb4c0594a6616e1350acd9d48b Mon Sep 17 00:00:00 2001 From: Jehan-Guillaume de Rorthais <j...@dalibo.com> Date: Mon, 12 Apr 2021 14:45:17 +0200 Subject: [PATCH] Draft: Makes PostgresNode multi-version aware --- src/test/perl/PostgresNode.pm | 708 ++++++++++++++++++++++++++++--- src/test/perl/PostgresVersion.pm | 65 +++ 2 files changed, 704 insertions(+), 69 deletions(-) create mode 100644 src/test/perl/PostgresVersion.pm diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm index e26b2b3f30..d7a9e54efd 100644 --- a/src/test/perl/PostgresNode.pm +++ b/src/test/perl/PostgresNode.pm @@ -102,6 +102,7 @@ use Test::More; use TestLib (); use Time::HiRes qw(usleep); use Scalar::Util qw(blessed); +use PostgresVersion; our @EXPORT = qw( get_new_node @@ -376,9 +377,42 @@ sub dump_info return; } +# Internal routine to allows streaming replication on a primary node. +sub allows_streaming +{ + my ($self, $allows_streaming) = @_; + my $pgdata = $self->data_dir; + my $wal_level; + + if ($allows_streaming eq "logical") + { + $wal_level = "logical"; + } + else + { + $wal_level = "replica"; + } + + $self->append_conf( 'postgresql.conf', qq{ + wal_level = $wal_level + max_wal_senders = 10 + max_replication_slots = 10 + wal_log_hints = on + hot_standby = on + # conservative settings to ensure we can run multiple postmasters: + shared_buffers = 1MB + max_connections = 10 + # limit disk space consumption, too: + max_wal_size = 128MB + }); -# Internal method to set up trusted pg_hba.conf for replication. Not -# documented because you shouldn't use it, it's called automatically if needed. + $self->set_replication_conf; + + return; +} + +# Internal to set up trusted pg_hba.conf for replication. Not documented +# because you shouldn't use it, it's called automatically if needed. sub set_replication_conf { my ($self) = @_; @@ -398,6 +432,15 @@ sub set_replication_conf return; } +# Internal methods to track capabilities depending on the PostgreSQL major +# version used + +sub can_slots { return 1 } +sub can_log_hints { return 1 } +sub can_restart_after_crash { return 1 } +sub can_skip_init_fsync { return 1 } + + =pod =item $node->init(...) @@ -429,6 +472,7 @@ sub init my $port = $self->port; my $pgdata = $self->data_dir; my $host = $self->host; + my @cmd; local %ENV = $self->_get_env(); @@ -438,19 +482,25 @@ sub init mkdir $self->backup_dir; mkdir $self->archive_dir; - TestLib::system_or_bail('initdb', '-D', $pgdata, '-A', 'trust', '-N', - @{ $params{extra} }); + @cmd = ( 'initdb', '-D', $pgdata, '-A', 'trust' ); + push @cmd, '-N' if $self->can_skip_init_fsync; + push @cmd, @{ $params{extra} } if defined $params{extra}; + + TestLib::system_or_bail(@cmd); 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 "restart_after_crash = off\n" if $self->can_restart_after_crash; 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"; + if ($self->version >= '9.5.0') + { + print $conf "log_replication_commands = on\n"; + print $conf "wal_retrieve_retry_interval = '500ms'\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 @@ -463,31 +513,8 @@ sub init # 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 "wal_level = minimal\n"; + print $conf "max_wal_senders = 0\n"; print $conf "port = $port\n"; if ($use_tcp) @@ -505,8 +532,8 @@ sub init 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}; + $self->allows_streaming($params{allows_streaming}) if $params{allows_streaming}; + $self->enable_archiving if $params{has_archiving}; return; } @@ -986,8 +1013,8 @@ primary_conninfo='$root_connstr' sub enable_restoring { my ($self, $root_node, $standby) = @_; - my $path = TestLib::perl2host($root_node->archive_dir); - my $name = $self->name; + my $path = TestLib::perl2host($root_node->archive_dir); + my $name = $self->name; print "### Enabling WAL restore for node \"$name\"\n"; @@ -1038,7 +1065,7 @@ sub set_recovery_mode =item $node->set_standby_mode() -Place standby.signal file. +Place standby.signal file or set standby_mode = on. =cut @@ -1193,6 +1220,13 @@ sub get_new_node $node->{_install_path} = $params{install_path}; } + # validate install path and set version + # FIXME: this should probably go to the constructor as various + # methods need to be able to read the cluster version + $node->_read_pg_config; + + bless $node, $node->version->get_class; + # Add node to list of nodes push(@all_nodes, $node); @@ -1270,6 +1304,48 @@ sub _get_env } return (%inst_env); } + +# 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, $inst) = @_; + 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); +} =pod @@ -1382,6 +1458,11 @@ END $? = $exit_code; } +sub version { + my $self = shift; + return $self->{_pg_version}; +} + =pod =item $node->teardown_node() @@ -1920,17 +2001,7 @@ sub connect_ok if (@log_like or @log_unlike) { # Don't let previous log entries match for this connection. - # On Windows, the truncation would not work, so rotate the log - # file before restarting the server afresh. - if ($TestLib::windows_os) - { - $self->rotate_logfile; - $self->restart; - } - else - { - truncate $self->logfile, 0; - } + truncate $self->logfile, 0; } # Never prompt for a password, any callers of this routine should @@ -2004,17 +2075,7 @@ sub connect_fails if (@log_like or @log_unlike) { # Don't let previous log entries match for this connection. - # On Windows, the truncation would not work, so rotate the log - # file before restarting the server afresh. - if ($TestLib::windows_os) - { - $self->rotate_logfile; - $self->restart; - } - else - { - truncate $self->logfile, 0; - } + truncate $self->logfile, 0; } # Never prompt for a password, any callers of this routine should @@ -2304,8 +2365,12 @@ This is not a test. It die()s on failure. sub wait_for_catchup { my ($self, $standby_name, $mode, $target_lsn) = @_; + my $lsn_expr; + my $query; + my %valid_modes; + $mode = defined($mode) ? $mode : 'replay'; - my %valid_modes = + %valid_modes = ('sent' => 1, 'write' => 1, 'flush' => 1, 'replay' => 1); croak "unknown mode $mode for 'wait_for_catchup', valid modes are " . join(', ', keys(%valid_modes)) @@ -2316,7 +2381,7 @@ sub wait_for_catchup { $standby_name = $standby_name->name; } - my $lsn_expr; + if (defined($target_lsn)) { $lsn_expr = "'$target_lsn'"; @@ -2325,16 +2390,19 @@ sub wait_for_catchup { $lsn_expr = 'pg_current_wal_lsn()'; } - print "Waiting for replication conn " - . $standby_name . "'s " - . $mode - . "_lsn to pass " - . $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';]; + + print "Waiting for replication conn ${standby_name}'s ${mode}_lsn " + . "to pass $lsn_expr on " . $self->name . "\n"; + + $query = qq[ + SELECT $lsn_expr <= ${mode}_lsn AND state = 'streaming' + FROM pg_catalog.pg_stat_replication + WHERE application_name = '$standby_name' + ]; + $self->poll_query_until('postgres', $query) or croak "timed out waiting for catchup"; + print "done\n"; return; } @@ -2556,4 +2624,506 @@ sub pg_recvlogical_upto =cut +package PostgresNode13; + +use strict; +use warnings; +use parent 'PostgresNode'; + + +package PostgresNode12; + +use strict; +use warnings; +use parent 'PostgresNode'; + +package PostgresNode11; + +use strict; +use warnings; +use Carp; +use parent 'PostgresNode'; + +# superuser_reserved_connections + max_wal_senders must be less +# than max_connections +sub allows_streaming +{ + my ($self, $allows_streaming) = @_; + my $pgdata = $self->data_dir; + my $wal_level; + + if ($allows_streaming eq "logical") + { + $wal_level = "logical"; + } + else + { + $wal_level = "replica"; + } + + $self->append_conf( 'postgresql.conf', qq{ + wal_level = $wal_level + max_wal_senders = 5 + max_replication_slots = 10 + wal_log_hints = on + hot_standby = on + # conservative settings to ensure we can run multiple postmasters: + shared_buffers = 1MB + max_connections = 10 + # limit disk space consumption, too: + max_wal_size = 128MB + }); + + $self->set_replication_conf; + + return; +} + +# standby mode set as GUC in recovery.conf file +sub set_standby_mode +{ + my ($self) = @_; + + $self->append_conf( 'recovery.conf', 'standby_mode = on'); + + return; +} + +# recovery mode when recovery.conf file exists with standby_mode = off +sub set_recovery_mode +{ + my ($self) = @_; + + $self->append_conf( 'recovery.conf', 'standby_mode = off'); + + return; +} + +# Internal routine to enable archive recovery command on a standby node +sub enable_restoring +{ + my ($self, $root_node, $standby) = @_; + my $path = TestLib::perl2host($root_node->archive_dir); + my $name = $self->name; + + print "### Enabling WAL restore for node \"$name\"\n"; + + # FIXME: use a dedicated command to build the restore command? + # On Windows, the path specified in the restore command needs to use + # double back-slashes to work properly and to be able to detect properly + # the file targeted by the copy command, so the directory value used + # in this routine, using only one back-slash, need to be properly changed + # first. Paths also need to be double-quoted to prevent failures where + # the path contains spaces. + $path =~ s{\\}{\\\\}g if ($TestLib::windows_os); + my $copy_command = + $TestLib::windows_os + ? qq{copy "$path\\\\%f" "%p"} + : qq{cp "$path/%f" "%p"}; + + $self->append_conf( + 'recovery.conf', qq( +restore_command = '$copy_command' +)); + if ($standby) + { + $self->set_standby_mode(); + } + else + { + $self->set_recovery_mode(); + } + return; +} + +# Internal routine to enable streaming replication on a standby node. +# * up to version 11, replication setting is in recovery.conf. +# * up to version 11, we need to explicitly set application_name as cluster_name +# is not used as a fallback. +sub enable_streaming +{ + my ($self, $root_node) = @_; + my $root_connstr = $root_node->connstr; + my $name = $self->name; + + print "### Enabling streaming replication for node \"$name\"\n"; + $self->append_conf( + 'recovery.conf', qq( +primary_conninfo='$root_connstr application_name=$name' +)); + $self->set_standby_mode(); + return; +} + +package PostgresNode10; + +use strict; +use warnings; +use parent -norequire, 'PostgresNode11'; + +package PostgresNode96; + +use strict; +use warnings; +use Carp; +use Scalar::Util qw(blessed); +use parent -norequire, 'PostgresNode11'; + +# pg_ctl doesn't wait by default for full-start in 9.6 and before +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); + + # 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", '-w', '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; +} + +# '--no-sync' doesn't exist in 9.6 and before +sub backup +{ + my ($self, $backup_name, %params) = @_; + my $backup_path = $self->backup_dir . '/' . $backup_name; + my $name = $self->name; + + local %ENV = $self->_get_env(); + + print "# Taking pg_basebackup $backup_name from node \"$name\"\n"; + TestLib::system_or_bail( + 'pg_basebackup', '-D', $backup_path, '-h', + $self->host, '-p', $self->port, '--checkpoint', + 'fast', @{ $params{backup_options} }); + print "# Backup finished\n"; + return; +} + +# need to explicitly allow replication in pg_hba.conf for 9.6 and before +sub set_replication_conf +{ + my ($self) = @_; + my $pgdata = $self->data_dir; + + $self->host eq $test_pghost + or croak "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 && !$TestLib::use_unix_sockets) + { + print $hba + "host replication all $test_localhost/32 sspi include_realm=1 map=regress\n"; + } + else { + print $hba "local replication all trust\n"; + } + + close $hba; + return; +} + +# various function have been renamed after 9.6 +sub lsn +{ + my ($self, $mode) = @_; + my %modes = ( + '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()'); + + $mode = '<undef>' unless defined $mode; + croak "unknown mode for 'lsn': '$mode', valid modes are " + . join(', ', keys %modes) + if !defined($modes{$mode}); + + my $result = $self->safe_psql('postgres', "SELECT $modes{$mode}"); + chomp($result); + if ($result eq '') + { + return; + } + else + { + return $result; + } +} + +# field renamed after 9.6 +sub wait_for_catchup +{ + my ($self, $standby_name, $mode, $target_lsn) = @_; + my $lsn_expr; + my $query; + my %valid_modes; + + $mode = defined($mode) ? $mode : 'replay'; + %valid_modes = + ('sent' => 1, 'write' => 1, 'flush' => 1, 'replay' => 1); + croak "unknown mode $mode for 'wait_for_catchup', valid modes are " + . join(', ', keys(%valid_modes)) + unless exists($valid_modes{$mode}); + + # Allow passing of a PostgresNode instance as shorthand + if (blessed($standby_name) && $standby_name->isa("PostgresNode")) + { + $standby_name = $standby_name->name; + } + + if (defined($target_lsn)) + { + $lsn_expr = "'$target_lsn'"; + } + else + { + $lsn_expr = 'pg_current_wal_lsn()'; + } + + print "Waiting for replication conn ${standby_name}'s ${mode}_location " + . "to pass $lsn_expr on " . $self->name . "\n"; + + $query = qq[ + SELECT $lsn_expr <= ${mode}_location AND state = 'streaming' + FROM pg_catalog.pg_stat_replication + WHERE application_name = '$standby_name' + ]; + + $self->poll_query_until('postgres', $query) + or croak "timed out waiting for catchup"; + + print "done\n"; + return; +} + + +package PostgresNode95; + +use strict; +use warnings; +use parent -norequire, 'PostgresNode96'; + +# "wal_level = replicat" does not exist in 9.5. +sub allows_streaming { + my $self = shift; + + $self->SUPER::allows_streaming(@_); + + $self->append_conf('postgresql.conf', 'wal_level = hot_standby'); + + return; +} + + +package PostgresNode94; + +use strict; +use warnings; +use parent -norequire, 'PostgresNode95'; +use Time::HiRes qw(usleep); + +# "max_wal_size" does not exists in 9.4. No need to lower "checkpoint_segments" +# as it is already low. +# Moreover, we can not just overload wal_level as 9.4 blocks on first bad +# GUC value. +sub allows_streaming +{ + my ($self, $allows_streaming) = @_; + my $pgdata = $self->data_dir; + my $wal_level; + + if ($allows_streaming eq "logical") + { + $wal_level = "logical"; + } + else + { + $wal_level = "hot_standby"; + } + + $self->append_conf( 'postgresql.conf', qq{ + wal_level = $wal_level + max_wal_senders = 5 + max_replication_slots = 10 + wal_log_hints = on + hot_standby = on + # conservative settings to ensure we can run multiple postmasters: + shared_buffers = 1MB + max_connections = 10 + }); + + $self->set_replication_conf; + + return; +} + +# * "cluster-name" not supported in 9.4 and before +# * pg_ctl -w does not wait for postmaster.pid to be ready +# see: f13ea95f9e473 +sub start +{ + my ($self, %params) = @_; + my $port = $self->port; + my $pgdata = $self->data_dir; + my $name = $self->name; + my $pidfile = $self->data_dir . "/postmaster.pid"; + my $max_attempts = 300; # 30s + 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); + + # 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, '-w', 'start'); + + while ($max_attempts and not -f $pidfile) + { + $max_attempts--; + # Wait 0.1 second before retrying. + usleep(100_000); + } + + 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; + } + + if (not -f $pidfile) { + print "# timeout while waiting for postmaster.pid; 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 PostgresNode93; + +use strict; +use warnings; +use parent -norequire, 'PostgresNode94'; + + +# "wal_level = logical" doesn't exists +sub allows_streaming +{ + my ($self, $allows_streaming) = @_; + my $pgdata = $self->data_dir; + + $self->append_conf( 'postgresql.conf', qq{ + wal_level = hot_standby + max_wal_senders = 5 + hot_standby = on + # conservative settings to ensure we can run multiple postmasters: + shared_buffers = 1MB + max_connections = 10 + }); + + $self->set_replication_conf; + + return; +} + +sub can_slots { return 0 } +sub can_log_hints { return 0 } + +package PostgresNode92; + +use strict; +use warnings; +use parent -norequire, 'PostgresNode93'; +use TestLib (); + +sub can_skip_init_fsync { return 0 } + +sub init +{ + my $self = shift; + + $self->SUPER::init(@_); + $self->rename_unix_socket_dir; + + return; +} + +sub init_from_backup +{ + my $self = shift; + + $self->SUPER::init_from_backup(@_); + $self->rename_unix_socket_dir; + + return; +} + +sub rename_unix_socket_dir +{ + my $self = shift; + my $file = $self->data_dir ."/postgresql.conf"; + my $conf; + + $conf = TestLib::slurp_file($file); + + $conf =~ s/unix_socket_directories/unix_socket_directory/g; + + open my $fd, '>', $file; + print $fd $conf; + close $fd; + + return; +} + +package PostgresNode91; + +use strict; +use warnings; +use parent -norequire, 'PostgresNode92'; + + +package PostgresNode90; + +use strict; +use warnings; +use parent -norequire, 'PostgresNode92'; + +sub can_restart_after_crash { return 0 } + 1; diff --git a/src/test/perl/PostgresVersion.pm b/src/test/perl/PostgresVersion.pm new file mode 100644 index 0000000000..ca8c0ffe6f --- /dev/null +++ b/src/test/perl/PostgresVersion.pm @@ -0,0 +1,65 @@ +#!/usr/bin/perl + +package PostgresVersion; + +use strict; +use warnings; +use Carp; + +use overload '""' => sub { $_[0]->{'version'} }, + #'>' => \&vers_gt, + '<=>' => \&vers_cmp; + +sub new { + my $class = shift; + my $ver = shift; + my $errmsg = "could not parse pg_config --version output: $ver"; + my $self; + + $self->{'vernum'} = 0; + $self->{'version'} = 0; + + $ver =~ /(?:PostgreSQL )?(\d+)\.(\d+)(?:\.(\d+))?/; + + croak $errmsg unless defined $1 and defined $2; + + $self->{'version'} = $1; + $self->{'vernum'} += 10000 * $1; + $self->{'version'} .= ".$2"; + + if ($self->{'vernum'} >= 100000) { + croak $errmsg if defined $3; + $self->{'vernum'} += $2; + $self->{'class'} = "$1"; + } + else { + #croak $errmsg unless defined $3; + my $minor = defined $3 ? $3 : 0; + $self->{'vernum'} += 100 * $2 + $minor; + $self->{'version'} .= ".$minor"; + $self->{'class'} = "$1$2" + } + + return bless $self, $class +} + +sub get_class { + my $self = shift; + return "PostgresNode$self->{'class'}"; +} + +sub vers_cmp { + my ($a, $b, $swapped) = @_; + my $res = 1; + + $b = PostgresVersion->new($b) unless ref($b) eq 'PostgresVersion'; + + return 0 if $a->{'vernum'} == $b->{'vernum'}; + $res = -1 if $a->{'vernum'} < $b->{'vernum'}; + + $res *= -1 if $swapped; + + return $res; +} + +1 -- 2.20.1