#################################################################################################################################### # DB MODULE #################################################################################################################################### package BackRest::Db; use threads; use strict; use warnings; use Carp; use Net::OpenSSH; use File::Basename; use IPC::System::Simple qw(capture); use lib dirname($0); use BackRest::Utility; #################################################################################################################################### # CONSTRUCTOR #################################################################################################################################### sub new { my $class = shift; # Class name my $strCommandPsql = shift; # PSQL command my $strDbHost = shift; # Database host name my $strDbUser = shift; # Database user name (generally postgres) # Create the class hash my $self = {}; bless $self, $class; # Initialize variables $self->{strCommandPsql} = $strCommandPsql; $self->{strDbHost} = $strDbHost; $self->{strDbUser} = $strDbUser; # Connect SSH object if db host is defined if (defined($self->{strDbHost}) && !defined($self->{oDbSSH})) { my $strOptionSSHRequestTTY = 'RequestTTY=yes'; &log(TRACE, "connecting to database ssh host $self->{strDbHost}"); # !!! This could be improved by redirecting stderr to a file to get a better error message $self->{oDbSSH} = Net::OpenSSH->new($self->{strDbHost}, user => $self->{strDbUser}, master_opts => [-o => $strOptionSSHRequestTTY]); $self->{oDbSSH}->error and confess &log(ERROR, "unable to connect to $self->{strDbHost}: " . $self->{oDbSSH}->error); } return $self; } #################################################################################################################################### # IS_REMOTE # # Determine whether database operations are remote. #################################################################################################################################### sub is_remote { my $self = shift; # If the SSH object is defined then db is remote return defined($self->{oDbSSH}) ? true : false; } #################################################################################################################################### # PSQL_EXECUTE #################################################################################################################################### sub psql_execute { my $self = shift; my $strScript = shift; # psql script to execute # Get the user-defined command for psql my $strCommand = $self->{strCommandPsql} . " -c \"${strScript}\" postgres"; my $strResult; # !!! Need to capture error output with open3 and log it # Run remotely if ($self->is_remote()) { &log(TRACE, "psql execute: remote ${strScript}"); $strResult = $self->{oDbSSH}->capture($strCommand) or confess &log(ERROR, "unable to execute remote psql command '${strCommand}'"); } # Else run locally else { &log(TRACE, "psql execute: ${strScript}"); $strResult = capture($strCommand) or confess &log(ERROR, "unable to execute local psql command '${strCommand}'"); } return $strResult; } #################################################################################################################################### # TABLESPACE_MAP_GET - Get the mapping between oid and tablespace name #################################################################################################################################### sub tablespace_map_get { my $self = shift; my $oHashRef = shift; data_hash_build($oHashRef, "oid\tname\n" . $self->psql_execute( 'copy (select oid, spcname from pg_tablespace) to stdout'), "\t"); } #################################################################################################################################### # DB_VERSION_GET #################################################################################################################################### sub db_version_get { my $self = shift; if (defined($self->{fVersion})) { return $self->{fVersion}; } $self->{fVersion} = trim($self->psql_execute("copy (select (regexp_matches(split_part(version(), ' ', 2), '^[0-9]+\.[0-9]+'))[1]) to stdout")); &log(DEBUG, "database version is $self->{fVersion}"); return $self->{fVersion}; } #################################################################################################################################### # BACKUP_START #################################################################################################################################### sub backup_start { my $self = shift; my $strLabel = shift; my $bStartFast = shift; my @stryField = split("\t", trim($self->psql_execute("set client_min_messages = 'warning';" . "copy (select to_char(current_timestamp, 'YYYY-MM-DD HH24:MI:SS.US TZ'), pg_xlogfile_name(xlog) from pg_start_backup('${strLabel}'" . ($bStartFast ? ', true' : '') . ') as xlog) to stdout'))); return $stryField[1], $stryField[0]; } #################################################################################################################################### # BACKUP_STOP #################################################################################################################################### sub backup_stop { my $self = shift; my @stryField = split("\t", trim($self->psql_execute("set client_min_messages = 'warning';" . "copy (select to_char(clock_timestamp(), 'YYYY-MM-DD HH24:MI:SS.US TZ'), pg_xlogfile_name(xlog) from pg_stop_backup() as xlog) to stdout"))); return $stryField[1], $stryField[0]; } 1;