mirror of https://github.com/postgres/postgres
Backport BackgroundPsql perl test module
Backport the new BackgroundPsql modules and the constructor functions,
background_psql() and interactive_psql, to all supported
branches. That makes it easier to backpatch tests that use it.
BackgroundPsql was introduced in version 16. On version 16, this
commit backports just the new timeout argument from master (commit
334f512f45
). On older branches, the whole facility. This includes the
change to `use warnings FATAL => 'all'`, which we haven't otherwise
backported, but it seems good to keep the file identical across
branches.
Discussion: https://www.postgresql.org/message-id/b7c64f20-ea01-4f15-9088-0cd6832af149@iki.fi
This commit is contained in:
parent
76fda61402
commit
d5fd7865f0
|
@ -36,63 +36,46 @@ $node->safe_psql('postgres', q(CREATE TABLE tbl(i int)));
|
||||||
# statements.
|
# statements.
|
||||||
#
|
#
|
||||||
|
|
||||||
my $main_in = '';
|
my $main_h = $node->background_psql('postgres');
|
||||||
my $main_out = '';
|
|
||||||
my $main_timer = IPC::Run::timeout($PostgreSQL::Test::Utils::timeout_default);
|
|
||||||
|
|
||||||
my $main_h =
|
$main_h->query_safe(q(
|
||||||
$node->background_psql('postgres', \$main_in, \$main_out,
|
|
||||||
$main_timer, on_error_stop => 1);
|
|
||||||
$main_in .= q(
|
|
||||||
BEGIN;
|
BEGIN;
|
||||||
INSERT INTO tbl VALUES(0);
|
INSERT INTO tbl VALUES(0);
|
||||||
\echo syncpoint1
|
));
|
||||||
);
|
|
||||||
pump $main_h until $main_out =~ /syncpoint1/ || $main_timer->is_expired;
|
|
||||||
|
|
||||||
my $cic_in = '';
|
my $cic_h = $node->background_psql('postgres');
|
||||||
my $cic_out = '';
|
|
||||||
my $cic_timer = IPC::Run::timeout($PostgreSQL::Test::Utils::timeout_default);
|
$cic_h->query_until(qr/start/, q(
|
||||||
my $cic_h =
|
|
||||||
$node->background_psql('postgres', \$cic_in, \$cic_out,
|
|
||||||
$cic_timer, on_error_stop => 1);
|
|
||||||
$cic_in .= q(
|
|
||||||
\echo start
|
\echo start
|
||||||
CREATE INDEX CONCURRENTLY idx ON tbl(i);
|
CREATE INDEX CONCURRENTLY idx ON tbl(i);
|
||||||
);
|
));
|
||||||
pump $cic_h until $cic_out =~ /start/ || $cic_timer->is_expired;
|
|
||||||
|
|
||||||
$main_in .= q(
|
$main_h->query_safe(q(
|
||||||
PREPARE TRANSACTION 'a';
|
PREPARE TRANSACTION 'a';
|
||||||
);
|
));
|
||||||
|
|
||||||
$main_in .= q(
|
$main_h->query_safe(q(
|
||||||
BEGIN;
|
BEGIN;
|
||||||
INSERT INTO tbl VALUES(0);
|
INSERT INTO tbl VALUES(0);
|
||||||
\echo syncpoint2
|
));
|
||||||
);
|
|
||||||
pump $main_h until $main_out =~ /syncpoint2/ || $main_timer->is_expired;
|
|
||||||
|
|
||||||
$node->safe_psql('postgres', q(COMMIT PREPARED 'a';));
|
$node->safe_psql('postgres', q(COMMIT PREPARED 'a';));
|
||||||
|
|
||||||
$main_in .= q(
|
$main_h->query_safe(q(
|
||||||
PREPARE TRANSACTION 'b';
|
PREPARE TRANSACTION 'b';
|
||||||
BEGIN;
|
BEGIN;
|
||||||
INSERT INTO tbl VALUES(0);
|
INSERT INTO tbl VALUES(0);
|
||||||
\echo syncpoint3
|
));
|
||||||
);
|
|
||||||
pump $main_h until $main_out =~ /syncpoint3/ || $main_timer->is_expired;
|
|
||||||
|
|
||||||
$node->safe_psql('postgres', q(COMMIT PREPARED 'b';));
|
$node->safe_psql('postgres', q(COMMIT PREPARED 'b';));
|
||||||
|
|
||||||
$main_in .= q(
|
$main_h->query_safe(q(
|
||||||
PREPARE TRANSACTION 'c';
|
PREPARE TRANSACTION 'c';
|
||||||
COMMIT PREPARED 'c';
|
COMMIT PREPARED 'c';
|
||||||
);
|
));
|
||||||
$main_h->pump_nb;
|
|
||||||
|
|
||||||
$main_h->finish;
|
$main_h->quit;
|
||||||
$cic_h->finish;
|
$cic_h->quit;
|
||||||
|
|
||||||
$result = $node->psql('postgres', q(SELECT bt_index_check('idx',true)));
|
$result = $node->psql('postgres', q(SELECT bt_index_check('idx',true)));
|
||||||
is($result, '0', 'bt_index_check after overlapping 2PC');
|
is($result, '0', 'bt_index_check after overlapping 2PC');
|
||||||
|
@ -113,22 +96,15 @@ PREPARE TRANSACTION 'persists_forever';
|
||||||
));
|
));
|
||||||
$node->restart;
|
$node->restart;
|
||||||
|
|
||||||
my $reindex_in = '';
|
my $reindex_h = $node->background_psql('postgres');
|
||||||
my $reindex_out = '';
|
$reindex_h->query_until(qr/start/, q(
|
||||||
my $reindex_timer =
|
|
||||||
IPC::Run::timeout($PostgreSQL::Test::Utils::timeout_default);
|
|
||||||
my $reindex_h =
|
|
||||||
$node->background_psql('postgres', \$reindex_in, \$reindex_out,
|
|
||||||
$reindex_timer, on_error_stop => 1);
|
|
||||||
$reindex_in .= q(
|
|
||||||
\echo start
|
\echo start
|
||||||
DROP INDEX CONCURRENTLY idx;
|
DROP INDEX CONCURRENTLY idx;
|
||||||
CREATE INDEX CONCURRENTLY idx ON tbl(i);
|
CREATE INDEX CONCURRENTLY idx ON tbl(i);
|
||||||
);
|
));
|
||||||
pump $reindex_h until $reindex_out =~ /start/ || $reindex_timer->is_expired;
|
|
||||||
|
|
||||||
$node->safe_psql('postgres', "COMMIT PREPARED 'spans_restart'");
|
$node->safe_psql('postgres', "COMMIT PREPARED 'spans_restart'");
|
||||||
$reindex_h->finish;
|
$reindex_h->quit;
|
||||||
$result = $node->psql('postgres', q(SELECT bt_index_check('idx',true)));
|
$result = $node->psql('postgres', q(SELECT bt_index_check('idx',true)));
|
||||||
is($result, '0', 'bt_index_check after 2PC and restart');
|
is($result, '0', 'bt_index_check after 2PC and restart');
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ use warnings;
|
||||||
use PostgreSQL::Test::Cluster;
|
use PostgreSQL::Test::Cluster;
|
||||||
use PostgreSQL::Test::Utils;
|
use PostgreSQL::Test::Utils;
|
||||||
use Test::More;
|
use Test::More;
|
||||||
use IPC::Run qw(pump finish timer);
|
|
||||||
use Data::Dumper;
|
use Data::Dumper;
|
||||||
|
|
||||||
# Do nothing unless Makefile has told us that the build is --with-readline.
|
# Do nothing unless Makefile has told us that the build is --with-readline.
|
||||||
|
@ -91,14 +90,7 @@ print $FH "other stuff\n";
|
||||||
close $FH;
|
close $FH;
|
||||||
|
|
||||||
# fire up an interactive psql session
|
# fire up an interactive psql session
|
||||||
my $in = '';
|
my $h = $node->interactive_psql('postgres');
|
||||||
my $out = '';
|
|
||||||
|
|
||||||
my $timer = timer($PostgreSQL::Test::Utils::timeout_default);
|
|
||||||
|
|
||||||
my $h = $node->interactive_psql('postgres', \$in, \$out, $timer);
|
|
||||||
|
|
||||||
like($out, qr/psql/, "print startup banner");
|
|
||||||
|
|
||||||
# Simple test case: type something and see if psql responds as expected
|
# Simple test case: type something and see if psql responds as expected
|
||||||
sub check_completion
|
sub check_completion
|
||||||
|
@ -108,15 +100,12 @@ sub check_completion
|
||||||
# report test failures from caller location
|
# report test failures from caller location
|
||||||
local $Test::Builder::Level = $Test::Builder::Level + 1;
|
local $Test::Builder::Level = $Test::Builder::Level + 1;
|
||||||
|
|
||||||
# reset output collector
|
|
||||||
$out = "";
|
|
||||||
# restart per-command timer
|
# restart per-command timer
|
||||||
$timer->start($PostgreSQL::Test::Utils::timeout_default);
|
$h->{timeout}->start($PostgreSQL::Test::Utils::timeout_default);
|
||||||
# send the data to be sent
|
|
||||||
$in .= $send;
|
# send the data to be sent and wait for its result
|
||||||
# wait ...
|
my $out = $h->query_until($pattern, $send);
|
||||||
pump $h until ($out =~ $pattern || $timer->is_expired);
|
my $okay = ($out =~ $pattern && !$h->{timeout}->is_expired);
|
||||||
my $okay = ($out =~ $pattern && !$timer->is_expired);
|
|
||||||
ok($okay, $annotation);
|
ok($okay, $annotation);
|
||||||
# for debugging, log actual output if it didn't match
|
# for debugging, log actual output if it didn't match
|
||||||
local $Data::Dumper::Terse = 1;
|
local $Data::Dumper::Terse = 1;
|
||||||
|
@ -442,10 +431,7 @@ check_completion("blarg \t\t", qr//, "check completion failure path");
|
||||||
clear_query();
|
clear_query();
|
||||||
|
|
||||||
# send psql an explicit \q to shut it down, else pty won't close properly
|
# send psql an explicit \q to shut it down, else pty won't close properly
|
||||||
$timer->start($PostgreSQL::Test::Utils::timeout_default);
|
$h->quit or die "psql returned $?";
|
||||||
$in .= "\\q\n";
|
|
||||||
finish $h or die "psql returned $?";
|
|
||||||
$timer->reset;
|
|
||||||
|
|
||||||
# done
|
# done
|
||||||
$node->stop;
|
$node->stop;
|
||||||
|
|
|
@ -0,0 +1,310 @@
|
||||||
|
|
||||||
|
# Copyright (c) 2021-2024, PostgreSQL Global Development Group
|
||||||
|
|
||||||
|
=pod
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
PostgreSQL::Test::BackgroundPsql - class for controlling background psql processes
|
||||||
|
|
||||||
|
=head1 SYNOPSIS
|
||||||
|
|
||||||
|
use PostgreSQL::Test::Cluster;
|
||||||
|
|
||||||
|
my $node = PostgreSQL::Test::Cluster->new('mynode');
|
||||||
|
|
||||||
|
# Create a data directory with initdb
|
||||||
|
$node->init();
|
||||||
|
|
||||||
|
# Start the PostgreSQL server
|
||||||
|
$node->start();
|
||||||
|
|
||||||
|
# Create and start an interactive psql session
|
||||||
|
my $isession = $node->interactive_psql('postgres');
|
||||||
|
# Apply timeout per query rather than per session
|
||||||
|
$isession->set_query_timer_restart();
|
||||||
|
# Run a query and get the output as seen by psql
|
||||||
|
my $ret = $isession->query("SELECT 1");
|
||||||
|
# Run a backslash command and wait until the prompt returns
|
||||||
|
$isession->query_until(qr/postgres #/, "\\d foo\n");
|
||||||
|
# Close the session and exit psql
|
||||||
|
$isession->quit;
|
||||||
|
|
||||||
|
# Create and start a background psql session
|
||||||
|
my $bsession = $node->background_psql('postgres');
|
||||||
|
|
||||||
|
# Run a query which is guaranteed to not return in case it fails
|
||||||
|
$bsession->query_safe("SELECT 1");
|
||||||
|
# Initiate a command which can be expected to terminate at a later stage
|
||||||
|
$bsession->query_until(qr/start/, q(
|
||||||
|
\echo start
|
||||||
|
CREATE INDEX CONCURRENTLY idx ON t(a);
|
||||||
|
));
|
||||||
|
# Close the session and exit psql
|
||||||
|
$bsession->quit;
|
||||||
|
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
|
PostgreSQL::Test::BackgroundPsql contains functionality for controlling
|
||||||
|
a background or interactive psql session operating on a PostgreSQL node
|
||||||
|
initiated by PostgreSQL::Test::Cluster.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
package PostgreSQL::Test::BackgroundPsql;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings FATAL => 'all';
|
||||||
|
|
||||||
|
use Carp;
|
||||||
|
use Config;
|
||||||
|
use IPC::Run;
|
||||||
|
use PostgreSQL::Test::Utils qw(pump_until);
|
||||||
|
use Test::More;
|
||||||
|
|
||||||
|
=pod
|
||||||
|
|
||||||
|
=head1 METHODS
|
||||||
|
|
||||||
|
=over
|
||||||
|
|
||||||
|
=item PostgreSQL::Test::BackgroundPsql->new(interactive, @psql_params, timeout)
|
||||||
|
|
||||||
|
Builds a new object of class C<PostgreSQL::Test::BackgroundPsql> for either
|
||||||
|
an interactive or background session and starts it. If C<interactive> is
|
||||||
|
true then a PTY will be attached. C<psql_params> should contain the full
|
||||||
|
command to run psql with all desired parameters and a complete connection
|
||||||
|
string. For C<interactive> sessions, IO::Pty is required.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub new
|
||||||
|
{
|
||||||
|
my $class = shift;
|
||||||
|
my ($interactive, $psql_params, $timeout) = @_;
|
||||||
|
my $psql = {
|
||||||
|
'stdin' => '',
|
||||||
|
'stdout' => '',
|
||||||
|
'stderr' => '',
|
||||||
|
'query_timer_restart' => undef
|
||||||
|
};
|
||||||
|
my $run;
|
||||||
|
|
||||||
|
# This constructor should only be called from PostgreSQL::Test::Cluster
|
||||||
|
my ($package, $file, $line) = caller;
|
||||||
|
die
|
||||||
|
"Forbidden caller of constructor: package: $package, file: $file:$line"
|
||||||
|
unless $package->isa('PostgreSQL::Test::Cluster');
|
||||||
|
|
||||||
|
$psql->{timeout} = IPC::Run::timeout(
|
||||||
|
defined($timeout)
|
||||||
|
? $timeout
|
||||||
|
: $PostgreSQL::Test::Utils::timeout_default);
|
||||||
|
|
||||||
|
if ($interactive)
|
||||||
|
{
|
||||||
|
$run = IPC::Run::start $psql_params,
|
||||||
|
'<pty<', \$psql->{stdin}, '>pty>', \$psql->{stdout}, '2>',
|
||||||
|
\$psql->{stderr},
|
||||||
|
$psql->{timeout};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$run = IPC::Run::start $psql_params,
|
||||||
|
'<', \$psql->{stdin}, '>', \$psql->{stdout}, '2>', \$psql->{stderr},
|
||||||
|
$psql->{timeout};
|
||||||
|
}
|
||||||
|
|
||||||
|
$psql->{run} = $run;
|
||||||
|
|
||||||
|
my $self = bless $psql, $class;
|
||||||
|
|
||||||
|
$self->_wait_connect();
|
||||||
|
|
||||||
|
return $self;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Internal routine for awaiting psql starting up and being ready to consume
|
||||||
|
# input.
|
||||||
|
sub _wait_connect
|
||||||
|
{
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
# Request some output, and pump until we see it. This means that psql
|
||||||
|
# connection failures are caught here, relieving callers of the need to
|
||||||
|
# handle those. (Right now, we have no particularly good handling for
|
||||||
|
# errors anyway, but that might be added later.)
|
||||||
|
my $banner = "background_psql: ready";
|
||||||
|
$self->{stdin} .= "\\echo $banner\n";
|
||||||
|
$self->{run}->pump()
|
||||||
|
until $self->{stdout} =~ /$banner/ || $self->{timeout}->is_expired;
|
||||||
|
$self->{stdout} = ''; # clear out banner
|
||||||
|
|
||||||
|
die "psql startup timed out" if $self->{timeout}->is_expired;
|
||||||
|
}
|
||||||
|
|
||||||
|
=pod
|
||||||
|
|
||||||
|
=item $session->quit
|
||||||
|
|
||||||
|
Close the session and clean up resources. Each test run must be closed with
|
||||||
|
C<quit>.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub quit
|
||||||
|
{
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
$self->{stdin} .= "\\q\n";
|
||||||
|
|
||||||
|
return $self->{run}->finish;
|
||||||
|
}
|
||||||
|
|
||||||
|
=pod
|
||||||
|
|
||||||
|
=item $session->reconnect_and_clear
|
||||||
|
|
||||||
|
Terminate the current session and connect again.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub reconnect_and_clear
|
||||||
|
{
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
# If psql isn't dead already, tell it to quit as \q, when already dead,
|
||||||
|
# causes IPC::Run to unhelpfully error out with "ack Broken pipe:".
|
||||||
|
$self->{run}->pump_nb();
|
||||||
|
if ($self->{run}->pumpable())
|
||||||
|
{
|
||||||
|
$self->{stdin} .= "\\q\n";
|
||||||
|
}
|
||||||
|
$self->{run}->finish;
|
||||||
|
|
||||||
|
# restart
|
||||||
|
$self->{run}->run();
|
||||||
|
$self->{stdin} = '';
|
||||||
|
$self->{stdout} = '';
|
||||||
|
|
||||||
|
$self->_wait_connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
=pod
|
||||||
|
|
||||||
|
=item $session->query()
|
||||||
|
|
||||||
|
Executes a query in the current session and returns the output in scalar
|
||||||
|
context and (output, error) in list context where error is 1 in case there
|
||||||
|
was output generated on stderr when executing the query.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub query
|
||||||
|
{
|
||||||
|
my ($self, $query) = @_;
|
||||||
|
my $ret;
|
||||||
|
my $output;
|
||||||
|
local $Test::Builder::Level = $Test::Builder::Level + 1;
|
||||||
|
|
||||||
|
note "issuing query via background psql: $query";
|
||||||
|
|
||||||
|
$self->{timeout}->start() if (defined($self->{query_timer_restart}));
|
||||||
|
|
||||||
|
# Feed the query to psql's stdin, followed by \n (so psql processes the
|
||||||
|
# line), by a ; (so that psql issues the query, if it doesn't include a ;
|
||||||
|
# itself), and a separator echoed with \echo, that we can wait on.
|
||||||
|
my $banner = "background_psql: QUERY_SEPARATOR";
|
||||||
|
$self->{stdin} .= "$query\n;\n\\echo $banner\n";
|
||||||
|
|
||||||
|
pump_until($self->{run}, $self->{timeout}, \$self->{stdout}, qr/$banner/);
|
||||||
|
|
||||||
|
die "psql query timed out" if $self->{timeout}->is_expired;
|
||||||
|
$output = $self->{stdout};
|
||||||
|
|
||||||
|
# remove banner again, our caller doesn't care
|
||||||
|
$output =~ s/\n$banner$//s;
|
||||||
|
|
||||||
|
# clear out output for the next query
|
||||||
|
$self->{stdout} = '';
|
||||||
|
|
||||||
|
$ret = $self->{stderr} eq "" ? 0 : 1;
|
||||||
|
|
||||||
|
return wantarray ? ($output, $ret) : $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
=pod
|
||||||
|
|
||||||
|
=item $session->query_safe()
|
||||||
|
|
||||||
|
Wrapper around C<query> which errors out if the query failed to execute.
|
||||||
|
Query failure is determined by it producing output on stderr.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub query_safe
|
||||||
|
{
|
||||||
|
my ($self, $query) = @_;
|
||||||
|
|
||||||
|
my $ret = $self->query($query);
|
||||||
|
|
||||||
|
if ($self->{stderr} ne "")
|
||||||
|
{
|
||||||
|
die "query failed: $self->{stderr}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
=pod
|
||||||
|
|
||||||
|
=item $session->query_until(until, query)
|
||||||
|
|
||||||
|
Issue C<query> and wait for C<until> appearing in the query output rather than
|
||||||
|
waiting for query completion. C<query> needs to end with newline and semicolon
|
||||||
|
(if applicable, interactive psql input may not require it) for psql to process
|
||||||
|
the input.
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub query_until
|
||||||
|
{
|
||||||
|
my ($self, $until, $query) = @_;
|
||||||
|
my $ret;
|
||||||
|
local $Test::Builder::Level = $Test::Builder::Level + 1;
|
||||||
|
|
||||||
|
$self->{timeout}->start() if (defined($self->{query_timer_restart}));
|
||||||
|
$self->{stdin} .= $query;
|
||||||
|
|
||||||
|
pump_until($self->{run}, $self->{timeout}, \$self->{stdout}, $until);
|
||||||
|
|
||||||
|
die "psql query timed out" if $self->{timeout}->is_expired;
|
||||||
|
|
||||||
|
$ret = $self->{stdout};
|
||||||
|
|
||||||
|
# clear out output for the next query
|
||||||
|
$self->{stdout} = '';
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
=pod
|
||||||
|
|
||||||
|
=item $session->set_query_timer_restart()
|
||||||
|
|
||||||
|
Configures the timer to be restarted before each query such that the defined
|
||||||
|
timeout is valid per query rather than per test run.
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub set_query_timer_restart
|
||||||
|
{
|
||||||
|
my $self = shift;
|
||||||
|
|
||||||
|
$self->{query_timer_restart} = 1;
|
||||||
|
return $self->{query_timer_restart};
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
|
@ -113,6 +113,7 @@ use PostgreSQL::Test::RecursiveCopy;
|
||||||
use Socket;
|
use Socket;
|
||||||
use Test::More;
|
use Test::More;
|
||||||
use PostgreSQL::Test::Utils ();
|
use PostgreSQL::Test::Utils ();
|
||||||
|
use PostgreSQL::Test::BackgroundPsql ();
|
||||||
use Time::HiRes qw(usleep);
|
use Time::HiRes qw(usleep);
|
||||||
use Scalar::Util qw(blessed);
|
use Scalar::Util qw(blessed);
|
||||||
|
|
||||||
|
@ -1956,18 +1957,9 @@ sub psql
|
||||||
|
|
||||||
=pod
|
=pod
|
||||||
|
|
||||||
=item $node->background_psql($dbname, \$stdin, \$stdout, $timer, %params) => harness
|
=item $node->background_psql($dbname, %params) => PostgreSQL::Test::BackgroundPsql instance
|
||||||
|
|
||||||
Invoke B<psql> on B<$dbname> and return an IPC::Run harness object, which the
|
Invoke B<psql> on B<$dbname> and return a BackgroundPsql object.
|
||||||
caller may use to send input to B<psql>. The process's stdin is sourced from
|
|
||||||
the $stdin scalar reference, and its stdout and stderr go to the $stdout
|
|
||||||
scalar reference. This allows the caller to act on other parts of the system
|
|
||||||
while idling this backend.
|
|
||||||
|
|
||||||
The specified timer object is attached to the harness, as well. It's caller's
|
|
||||||
responsibility to set the timeout length (usually
|
|
||||||
$PostgreSQL::Test::Utils::timeout_default), and to restart the timer after
|
|
||||||
each command if the timeout is per-command.
|
|
||||||
|
|
||||||
psql is invoked in tuples-only unaligned mode with reading of B<.psqlrc>
|
psql is invoked in tuples-only unaligned mode with reading of B<.psqlrc>
|
||||||
disabled. That may be overridden by passing extra psql parameters.
|
disabled. That may be overridden by passing extra psql parameters.
|
||||||
|
@ -1976,7 +1968,7 @@ Dies on failure to invoke psql, or if psql fails to connect. Errors occurring
|
||||||
later are the caller's problem. psql runs with on_error_stop by default so
|
later are the caller's problem. psql runs with on_error_stop by default so
|
||||||
that it will stop running sql and return 3 if passed SQL results in an error.
|
that it will stop running sql and return 3 if passed SQL results in an error.
|
||||||
|
|
||||||
Be sure to "finish" the harness when done with it.
|
Be sure to "quit" the returned object when done with it.
|
||||||
|
|
||||||
=over
|
=over
|
||||||
|
|
||||||
|
@ -1986,6 +1978,11 @@ By default, the B<psql> method invokes the B<psql> program with ON_ERROR_STOP=1
|
||||||
set, so SQL execution is stopped at the first error and exit code 3 is
|
set, so SQL execution is stopped at the first error and exit code 3 is
|
||||||
returned. Set B<on_error_stop> to 0 to ignore errors instead.
|
returned. Set B<on_error_stop> to 0 to ignore errors instead.
|
||||||
|
|
||||||
|
=item timeout => 'interval'
|
||||||
|
|
||||||
|
Set a timeout for a background psql session. By default, timeout of
|
||||||
|
$PostgreSQL::Test::Utils::timeout_default is set up.
|
||||||
|
|
||||||
=item replication => B<value>
|
=item replication => B<value>
|
||||||
|
|
||||||
If set, add B<replication=value> to the conninfo string.
|
If set, add B<replication=value> to the conninfo string.
|
||||||
|
@ -2002,11 +1999,12 @@ If given, it must be an array reference containing additional parameters to B<ps
|
||||||
|
|
||||||
sub background_psql
|
sub background_psql
|
||||||
{
|
{
|
||||||
my ($self, $dbname, $stdin, $stdout, $timer, %params) = @_;
|
my ($self, $dbname, %params) = @_;
|
||||||
|
|
||||||
local %ENV = $self->_get_env();
|
local %ENV = $self->_get_env();
|
||||||
|
|
||||||
my $replication = $params{replication};
|
my $replication = $params{replication};
|
||||||
|
my $timeout = undef;
|
||||||
|
|
||||||
my @psql_params = (
|
my @psql_params = (
|
||||||
$self->installed_command('psql'),
|
$self->installed_command('psql'),
|
||||||
|
@ -2018,46 +2016,23 @@ sub background_psql
|
||||||
'-');
|
'-');
|
||||||
|
|
||||||
$params{on_error_stop} = 1 unless defined $params{on_error_stop};
|
$params{on_error_stop} = 1 unless defined $params{on_error_stop};
|
||||||
|
$timeout = $params{timeout} if defined $params{timeout};
|
||||||
|
|
||||||
push @psql_params, '-v', 'ON_ERROR_STOP=1' if $params{on_error_stop};
|
push @psql_params, '-v', 'ON_ERROR_STOP=1' if $params{on_error_stop};
|
||||||
push @psql_params, @{ $params{extra_params} }
|
push @psql_params, @{ $params{extra_params} }
|
||||||
if defined $params{extra_params};
|
if defined $params{extra_params};
|
||||||
|
|
||||||
# Ensure there is no data waiting to be sent:
|
return PostgreSQL::Test::BackgroundPsql->new(0, \@psql_params, $timeout);
|
||||||
$$stdin = "" if ref($stdin);
|
|
||||||
# IPC::Run would otherwise append to existing contents:
|
|
||||||
$$stdout = "" if ref($stdout);
|
|
||||||
|
|
||||||
my $harness = IPC::Run::start \@psql_params,
|
|
||||||
'<', $stdin, '>', $stdout, $timer;
|
|
||||||
|
|
||||||
# Request some output, and pump until we see it. This means that psql
|
|
||||||
# connection failures are caught here, relieving callers of the need to
|
|
||||||
# handle those. (Right now, we have no particularly good handling for
|
|
||||||
# errors anyway, but that might be added later.)
|
|
||||||
my $banner = "background_psql: ready";
|
|
||||||
$$stdin = "\\echo $banner\n";
|
|
||||||
pump $harness until $$stdout =~ /$banner/ || $timer->is_expired;
|
|
||||||
|
|
||||||
die "psql startup timed out" if $timer->is_expired;
|
|
||||||
|
|
||||||
return $harness;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
=pod
|
=pod
|
||||||
|
|
||||||
=item $node->interactive_psql($dbname, \$stdin, \$stdout, $timer, %params) => harness
|
=item $node->interactive_psql($dbname, %params) => BackgroundPsql instance
|
||||||
|
|
||||||
Invoke B<psql> on B<$dbname> and return an IPC::Run harness object,
|
Invoke B<psql> on B<$dbname> and return a BackgroundPsql object, which the
|
||||||
which the caller may use to send interactive input to B<psql>.
|
caller may use to send interactive input to B<psql>.
|
||||||
The process's stdin is sourced from the $stdin scalar reference,
|
|
||||||
and its stdout and stderr go to the $stdout scalar reference.
|
|
||||||
ptys are used so that psql thinks it's being called interactively.
|
|
||||||
|
|
||||||
The specified timer object is attached to the harness, as well. It's caller's
|
A timeout of $PostgreSQL::Test::Utils::timeout_default is set up.
|
||||||
responsibility to set the timeout length (usually
|
|
||||||
$PostgreSQL::Test::Utils::timeout_default), and to restart the timer after
|
|
||||||
each command if the timeout is per-command.
|
|
||||||
|
|
||||||
psql is invoked in tuples-only unaligned mode with reading of B<.psqlrc>
|
psql is invoked in tuples-only unaligned mode with reading of B<.psqlrc>
|
||||||
disabled. That may be overridden by passing extra psql parameters.
|
disabled. That may be overridden by passing extra psql parameters.
|
||||||
|
@ -2065,9 +2040,7 @@ disabled. That may be overridden by passing extra psql parameters.
|
||||||
Dies on failure to invoke psql, or if psql fails to connect.
|
Dies on failure to invoke psql, or if psql fails to connect.
|
||||||
Errors occurring later are the caller's problem.
|
Errors occurring later are the caller's problem.
|
||||||
|
|
||||||
Be sure to "finish" the harness when done with it.
|
Be sure to "quit" the returned object when done with it.
|
||||||
|
|
||||||
The only extra parameter currently accepted is
|
|
||||||
|
|
||||||
=over
|
=over
|
||||||
|
|
||||||
|
@ -2083,7 +2056,7 @@ This requires IO::Pty in addition to IPC::Run.
|
||||||
|
|
||||||
sub interactive_psql
|
sub interactive_psql
|
||||||
{
|
{
|
||||||
my ($self, $dbname, $stdin, $stdout, $timer, %params) = @_;
|
my ($self, $dbname, %params) = @_;
|
||||||
|
|
||||||
local %ENV = $self->_get_env();
|
local %ENV = $self->_get_env();
|
||||||
|
|
||||||
|
@ -2094,26 +2067,7 @@ sub interactive_psql
|
||||||
push @psql_params, @{ $params{extra_params} }
|
push @psql_params, @{ $params{extra_params} }
|
||||||
if defined $params{extra_params};
|
if defined $params{extra_params};
|
||||||
|
|
||||||
# Ensure there is no data waiting to be sent:
|
return PostgreSQL::Test::BackgroundPsql->new(1, \@psql_params);
|
||||||
$$stdin = "" if ref($stdin);
|
|
||||||
# IPC::Run would otherwise append to existing contents:
|
|
||||||
$$stdout = "" if ref($stdout);
|
|
||||||
|
|
||||||
my $harness = IPC::Run::start \@psql_params,
|
|
||||||
'<pty<', $stdin, '>pty>', $stdout, $timer;
|
|
||||||
|
|
||||||
# Pump until we see psql's help banner. This ensures that callers
|
|
||||||
# won't write anything to the pty before it's ready, avoiding an
|
|
||||||
# implementation issue in IPC::Run. Also, it means that psql
|
|
||||||
# connection failures are caught here, relieving callers of
|
|
||||||
# the need to handle those. (Right now, we have no particularly
|
|
||||||
# good handling for errors anyway, but that might be added later.)
|
|
||||||
pump $harness
|
|
||||||
until $$stdout =~ /Type "help" for help/ || $timer->is_expired;
|
|
||||||
|
|
||||||
die "psql startup timed out" if $timer->is_expired;
|
|
||||||
|
|
||||||
return $harness;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Common sub of pgbench-invoking interfaces. Makes any requested script files
|
# Common sub of pgbench-invoking interfaces. Makes any requested script files
|
||||||
|
|
|
@ -28,7 +28,6 @@ use PostgreSQL::Test::Cluster;
|
||||||
use PostgreSQL::Test::Utils;
|
use PostgreSQL::Test::Utils;
|
||||||
use Test::More;
|
use Test::More;
|
||||||
use File::Copy;
|
use File::Copy;
|
||||||
use IPC::Run ();
|
|
||||||
use Scalar::Util qw(blessed);
|
use Scalar::Util qw(blessed);
|
||||||
|
|
||||||
my ($stdout, $stderr, $ret);
|
my ($stdout, $stderr, $ret);
|
||||||
|
|
|
@ -69,14 +69,8 @@ $node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
|
||||||
|
|
||||||
|
|
||||||
# a longrunning psql that we can use to trigger conflicts
|
# a longrunning psql that we can use to trigger conflicts
|
||||||
my $psql_timeout = IPC::Run::timer($PostgreSQL::Test::Utils::timeout_default);
|
my $psql_standby = $node_standby->background_psql($test_db,
|
||||||
my %psql_standby = ('stdin' => '', 'stdout' => '');
|
on_error_stop => 0);
|
||||||
$psql_standby{run} =
|
|
||||||
$node_standby->background_psql($test_db, \$psql_standby{stdin},
|
|
||||||
\$psql_standby{stdout},
|
|
||||||
$psql_timeout);
|
|
||||||
$psql_standby{stdout} = '';
|
|
||||||
|
|
||||||
my $expected_conflicts = 0;
|
my $expected_conflicts = 0;
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,15 +99,14 @@ my $cursor1 = "test_recovery_conflict_cursor";
|
||||||
|
|
||||||
# DECLARE and use a cursor on standby, causing buffer with the only block of
|
# DECLARE and use a cursor on standby, causing buffer with the only block of
|
||||||
# the relation to be pinned on the standby
|
# the relation to be pinned on the standby
|
||||||
$psql_standby{stdin} .= qq[
|
my $res = $psql_standby->query_safe(qq[
|
||||||
BEGIN;
|
BEGIN;
|
||||||
DECLARE $cursor1 CURSOR FOR SELECT b FROM $table1;
|
DECLARE $cursor1 CURSOR FOR SELECT b FROM $table1;
|
||||||
FETCH FORWARD FROM $cursor1;
|
FETCH FORWARD FROM $cursor1;
|
||||||
];
|
]);
|
||||||
# FETCH FORWARD should have returned a 0 since all values of b in the table
|
# FETCH FORWARD should have returned a 0 since all values of b in the table
|
||||||
# are 0
|
# are 0
|
||||||
ok(pump_until_standby(qr/^0$/m),
|
like($res, qr/^0$/m, "$sect: cursor with conflicting pin established");
|
||||||
"$sect: cursor with conflicting pin established");
|
|
||||||
|
|
||||||
# to check the log starting now for recovery conflict messages
|
# to check the log starting now for recovery conflict messages
|
||||||
my $log_location = -s $node_standby->logfile;
|
my $log_location = -s $node_standby->logfile;
|
||||||
|
@ -129,7 +122,7 @@ $primary_lsn = $node_primary->lsn('flush');
|
||||||
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
|
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
|
||||||
|
|
||||||
check_conflict_log("User was holding shared buffer pin for too long");
|
check_conflict_log("User was holding shared buffer pin for too long");
|
||||||
reconnect_and_clear();
|
$psql_standby->reconnect_and_clear();
|
||||||
check_conflict_stat("bufferpin");
|
check_conflict_stat("bufferpin");
|
||||||
|
|
||||||
|
|
||||||
|
@ -143,15 +136,12 @@ $primary_lsn = $node_primary->lsn('flush');
|
||||||
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
|
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
|
||||||
|
|
||||||
# DECLARE and FETCH from cursor on the standby
|
# DECLARE and FETCH from cursor on the standby
|
||||||
$psql_standby{stdin} .= qq[
|
$res = $psql_standby->query_safe(qq[
|
||||||
BEGIN;
|
BEGIN;
|
||||||
DECLARE $cursor1 CURSOR FOR SELECT b FROM $table1;
|
DECLARE $cursor1 CURSOR FOR SELECT b FROM $table1;
|
||||||
FETCH FORWARD FROM $cursor1;
|
FETCH FORWARD FROM $cursor1;
|
||||||
];
|
]);
|
||||||
ok( pump_until(
|
like($res, qr/^0$/m, "$sect: cursor with conflicting snapshot established");
|
||||||
$psql_standby{run}, $psql_timeout,
|
|
||||||
\$psql_standby{stdout}, qr/^0$/m,),
|
|
||||||
"$sect: cursor with conflicting snapshot established");
|
|
||||||
|
|
||||||
# Do some HOT updates
|
# Do some HOT updates
|
||||||
$node_primary->safe_psql($test_db,
|
$node_primary->safe_psql($test_db,
|
||||||
|
@ -166,7 +156,7 @@ $node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
|
||||||
|
|
||||||
check_conflict_log(
|
check_conflict_log(
|
||||||
"User query might have needed to see row versions that must be removed");
|
"User query might have needed to see row versions that must be removed");
|
||||||
reconnect_and_clear();
|
$psql_standby->reconnect_and_clear();
|
||||||
check_conflict_stat("snapshot");
|
check_conflict_stat("snapshot");
|
||||||
|
|
||||||
|
|
||||||
|
@ -175,12 +165,12 @@ $sect = "lock conflict";
|
||||||
$expected_conflicts++;
|
$expected_conflicts++;
|
||||||
|
|
||||||
# acquire lock to conflict with
|
# acquire lock to conflict with
|
||||||
$psql_standby{stdin} .= qq[
|
$res = $psql_standby->query_safe(qq[
|
||||||
BEGIN;
|
BEGIN;
|
||||||
LOCK TABLE $table1 IN ACCESS SHARE MODE;
|
LOCK TABLE $table1 IN ACCESS SHARE MODE;
|
||||||
SELECT 1;
|
SELECT 1;
|
||||||
];
|
]);
|
||||||
ok(pump_until_standby(qr/^1$/m), "$sect: conflicting lock acquired");
|
like($res, qr/^1$/m, "$sect: conflicting lock acquired");
|
||||||
|
|
||||||
# DROP TABLE containing block which standby has in a pinned buffer
|
# DROP TABLE containing block which standby has in a pinned buffer
|
||||||
$node_primary->safe_psql($test_db, qq[DROP TABLE $table1;]);
|
$node_primary->safe_psql($test_db, qq[DROP TABLE $table1;]);
|
||||||
|
@ -189,7 +179,7 @@ $primary_lsn = $node_primary->lsn('flush');
|
||||||
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
|
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
|
||||||
|
|
||||||
check_conflict_log("User was holding a relation lock for too long");
|
check_conflict_log("User was holding a relation lock for too long");
|
||||||
reconnect_and_clear();
|
$psql_standby->reconnect_and_clear();
|
||||||
check_conflict_stat("lock");
|
check_conflict_stat("lock");
|
||||||
|
|
||||||
|
|
||||||
|
@ -200,14 +190,14 @@ $expected_conflicts++;
|
||||||
# DECLARE a cursor for a query which, with sufficiently low work_mem, will
|
# DECLARE a cursor for a query which, with sufficiently low work_mem, will
|
||||||
# spill tuples into temp files in the temporary tablespace created during
|
# spill tuples into temp files in the temporary tablespace created during
|
||||||
# setup.
|
# setup.
|
||||||
$psql_standby{stdin} .= qq[
|
$res = $psql_standby->query_safe(qq[
|
||||||
BEGIN;
|
BEGIN;
|
||||||
SET work_mem = '64kB';
|
SET work_mem = '64kB';
|
||||||
DECLARE $cursor1 CURSOR FOR
|
DECLARE $cursor1 CURSOR FOR
|
||||||
SELECT count(*) FROM generate_series(1,6000);
|
SELECT count(*) FROM generate_series(1,6000);
|
||||||
FETCH FORWARD FROM $cursor1;
|
FETCH FORWARD FROM $cursor1;
|
||||||
];
|
]);
|
||||||
ok(pump_until_standby(qr/^6000$/m),
|
like($res, qr/^6000$/m,
|
||||||
"$sect: cursor with conflicting temp file established");
|
"$sect: cursor with conflicting temp file established");
|
||||||
|
|
||||||
# Drop the tablespace currently containing spill files for the query on the
|
# Drop the tablespace currently containing spill files for the query on the
|
||||||
|
@ -219,7 +209,7 @@ $node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
|
||||||
|
|
||||||
check_conflict_log(
|
check_conflict_log(
|
||||||
"User was or might have been using tablespace that must be dropped");
|
"User was or might have been using tablespace that must be dropped");
|
||||||
reconnect_and_clear();
|
$psql_standby->reconnect_and_clear();
|
||||||
check_conflict_stat("tablespace");
|
check_conflict_stat("tablespace");
|
||||||
|
|
||||||
|
|
||||||
|
@ -235,7 +225,7 @@ $node_standby->adjust_conf(
|
||||||
'max_standby_streaming_delay',
|
'max_standby_streaming_delay',
|
||||||
"${PostgreSQL::Test::Utils::timeout_default}s");
|
"${PostgreSQL::Test::Utils::timeout_default}s");
|
||||||
$node_standby->restart();
|
$node_standby->restart();
|
||||||
reconnect_and_clear();
|
$psql_standby->reconnect_and_clear();
|
||||||
|
|
||||||
# Generate a few dead rows, to later be cleaned up by vacuum. Then acquire a
|
# Generate a few dead rows, to later be cleaned up by vacuum. Then acquire a
|
||||||
# lock on another relation in a prepared xact, so it's held continuously by
|
# lock on another relation in a prepared xact, so it's held continuously by
|
||||||
|
@ -259,19 +249,15 @@ SELECT txid_current();
|
||||||
$primary_lsn = $node_primary->lsn('flush');
|
$primary_lsn = $node_primary->lsn('flush');
|
||||||
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
|
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
|
||||||
|
|
||||||
$psql_standby{stdin} .= qq[
|
$res = $psql_standby->query_until(qr/^1$/m, qq[
|
||||||
BEGIN;
|
BEGIN;
|
||||||
-- hold pin
|
-- hold pin
|
||||||
DECLARE $cursor1 CURSOR FOR SELECT a FROM $table1;
|
DECLARE $cursor1 CURSOR FOR SELECT a FROM $table1;
|
||||||
FETCH FORWARD FROM $cursor1;
|
FETCH FORWARD FROM $cursor1;
|
||||||
-- wait for lock held by prepared transaction
|
-- wait for lock held by prepared transaction
|
||||||
SELECT * FROM $table2;
|
SELECT * FROM $table2;
|
||||||
];
|
]);
|
||||||
ok( pump_until(
|
ok( 1, "$sect: cursor holding conflicting pin, also waiting for lock, established");
|
||||||
$psql_standby{run}, $psql_timeout,
|
|
||||||
\$psql_standby{stdout}, qr/^1$/m,),
|
|
||||||
"$sect: cursor holding conflicting pin, also waiting for lock, established"
|
|
||||||
);
|
|
||||||
|
|
||||||
# just to make sure we're waiting for lock already
|
# just to make sure we're waiting for lock already
|
||||||
ok( $node_standby->poll_query_until(
|
ok( $node_standby->poll_query_until(
|
||||||
|
@ -287,7 +273,7 @@ $primary_lsn = $node_primary->lsn('flush');
|
||||||
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
|
$node_primary->wait_for_catchup($node_standby, 'replay', $primary_lsn);
|
||||||
|
|
||||||
check_conflict_log("User transaction caused buffer deadlock with recovery.");
|
check_conflict_log("User transaction caused buffer deadlock with recovery.");
|
||||||
reconnect_and_clear();
|
$psql_standby->reconnect_and_clear();
|
||||||
check_conflict_stat("deadlock");
|
check_conflict_stat("deadlock");
|
||||||
|
|
||||||
# clean up for next tests
|
# clean up for next tests
|
||||||
|
@ -295,7 +281,7 @@ $node_primary->safe_psql($test_db, qq[ROLLBACK PREPARED 'lock';]);
|
||||||
$node_standby->adjust_conf('postgresql.conf', 'max_standby_streaming_delay',
|
$node_standby->adjust_conf('postgresql.conf', 'max_standby_streaming_delay',
|
||||||
'50ms');
|
'50ms');
|
||||||
$node_standby->restart();
|
$node_standby->restart();
|
||||||
reconnect_and_clear();
|
$psql_standby->reconnect_and_clear();
|
||||||
|
|
||||||
|
|
||||||
# Check that expected number of conflicts show in pg_stat_database. Needs to
|
# Check that expected number of conflicts show in pg_stat_database. Needs to
|
||||||
|
@ -320,8 +306,7 @@ check_conflict_log("User was connected to a database that must be dropped");
|
||||||
|
|
||||||
# explicitly shut down psql instances gracefully - to avoid hangs or worse on
|
# explicitly shut down psql instances gracefully - to avoid hangs or worse on
|
||||||
# windows
|
# windows
|
||||||
$psql_standby{stdin} .= "\\q\n";
|
$psql_standby->quit;
|
||||||
$psql_standby{run}->finish;
|
|
||||||
|
|
||||||
$node_standby->stop();
|
$node_standby->stop();
|
||||||
$node_primary->stop();
|
$node_primary->stop();
|
||||||
|
@ -329,37 +314,6 @@ $node_primary->stop();
|
||||||
|
|
||||||
done_testing();
|
done_testing();
|
||||||
|
|
||||||
|
|
||||||
sub pump_until_standby
|
|
||||||
{
|
|
||||||
my $match = shift;
|
|
||||||
|
|
||||||
return pump_until($psql_standby{run}, $psql_timeout,
|
|
||||||
\$psql_standby{stdout}, $match);
|
|
||||||
}
|
|
||||||
|
|
||||||
sub reconnect_and_clear
|
|
||||||
{
|
|
||||||
# If psql isn't dead already, tell it to quit as \q, when already dead,
|
|
||||||
# causes IPC::Run to unhelpfully error out with "ack Broken pipe:".
|
|
||||||
$psql_standby{run}->pump_nb();
|
|
||||||
if ($psql_standby{run}->pumpable())
|
|
||||||
{
|
|
||||||
$psql_standby{stdin} .= "\\q\n";
|
|
||||||
}
|
|
||||||
$psql_standby{run}->finish;
|
|
||||||
|
|
||||||
# restart
|
|
||||||
$psql_standby{run}->run();
|
|
||||||
$psql_standby{stdin} = '';
|
|
||||||
$psql_standby{stdout} = '';
|
|
||||||
|
|
||||||
# Run query to ensure connection has finished re-establishing
|
|
||||||
$psql_standby{stdin} .= qq[SELECT 1;\n];
|
|
||||||
die unless pump_until_standby(qr/^1$/m);
|
|
||||||
$psql_standby{stdout} = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
sub check_conflict_log
|
sub check_conflict_log
|
||||||
{
|
{
|
||||||
my $message = shift;
|
my $message = shift;
|
||||||
|
|
|
@ -85,39 +85,20 @@ is($node->psql('postgres', 'DROP DATABASE regression_invalid'),
|
||||||
# interruption happens at the appropriate moment, we lock pg_tablespace. DROP
|
# interruption happens at the appropriate moment, we lock pg_tablespace. DROP
|
||||||
# DATABASE scans pg_tablespace once it has reached the "irreversible" part of
|
# DATABASE scans pg_tablespace once it has reached the "irreversible" part of
|
||||||
# dropping the database, making it a suitable point to wait.
|
# dropping the database, making it a suitable point to wait.
|
||||||
my $bgpsql_in = '';
|
my $bgpsql = $node->background_psql('postgres', on_error_stop => 0);
|
||||||
my $bgpsql_out = '';
|
my $pid = $bgpsql->query('SELECT pg_backend_pid()');
|
||||||
my $bgpsql_err = '';
|
|
||||||
my $bgpsql_timer = IPC::Run::timer($PostgreSQL::Test::Utils::timeout_default);
|
|
||||||
my $bgpsql = $node->background_psql('postgres', \$bgpsql_in, \$bgpsql_out,
|
|
||||||
$bgpsql_timer, on_error_stop => 0);
|
|
||||||
$bgpsql_out = '';
|
|
||||||
$bgpsql_in .= "SELECT pg_backend_pid();\n";
|
|
||||||
|
|
||||||
pump_until($bgpsql, $bgpsql_timer, \$bgpsql_out, qr/\d/);
|
|
||||||
|
|
||||||
my $pid = $bgpsql_out;
|
|
||||||
$bgpsql_out = '';
|
|
||||||
|
|
||||||
# create the database, prevent drop database via lock held by a 2PC transaction
|
# create the database, prevent drop database via lock held by a 2PC transaction
|
||||||
$bgpsql_in .= qq(
|
ok( $bgpsql->query_safe(
|
||||||
|
qq(
|
||||||
CREATE DATABASE regression_invalid_interrupt;
|
CREATE DATABASE regression_invalid_interrupt;
|
||||||
BEGIN;
|
BEGIN;
|
||||||
LOCK pg_tablespace;
|
LOCK pg_tablespace;
|
||||||
PREPARE TRANSACTION 'lock_tblspc';
|
PREPARE TRANSACTION 'lock_tblspc';)),
|
||||||
\\echo done
|
|
||||||
);
|
|
||||||
|
|
||||||
ok(pump_until($bgpsql, $bgpsql_timer, \$bgpsql_out, qr/done/),
|
|
||||||
"blocked DROP DATABASE completion");
|
"blocked DROP DATABASE completion");
|
||||||
$bgpsql_out = '';
|
|
||||||
|
|
||||||
# Try to drop. This will wait due to the still held lock.
|
# Try to drop. This will wait due to the still held lock.
|
||||||
$bgpsql_in .= qq(
|
$bgpsql->query_until(qr//, "DROP DATABASE regression_invalid_interrupt;\n");
|
||||||
DROP DATABASE regression_invalid_interrupt;
|
|
||||||
\\echo DROP DATABASE completed
|
|
||||||
);
|
|
||||||
$bgpsql->pump_nb;
|
|
||||||
|
|
||||||
# Ensure we're waiting for the lock
|
# Ensure we're waiting for the lock
|
||||||
$node->poll_query_until('postgres',
|
$node->poll_query_until('postgres',
|
||||||
|
@ -130,9 +111,10 @@ ok($node->safe_psql('postgres', "SELECT pg_cancel_backend($pid)"),
|
||||||
|
|
||||||
# wait for cancellation to be processed
|
# wait for cancellation to be processed
|
||||||
ok( pump_until(
|
ok( pump_until(
|
||||||
$bgpsql, $bgpsql_timer, \$bgpsql_out, qr/DROP DATABASE completed/),
|
$bgpsql->{run}, $bgpsql->{timeout},
|
||||||
|
\$bgpsql->{stderr}, qr/canceling statement due to user request/),
|
||||||
"cancel processed");
|
"cancel processed");
|
||||||
$bgpsql_out = '';
|
$bgpsql->{stderr} = '';
|
||||||
|
|
||||||
# verify that connection to the database aren't allowed
|
# verify that connection to the database aren't allowed
|
||||||
is($node->psql('regression_invalid_interrupt', ''),
|
is($node->psql('regression_invalid_interrupt', ''),
|
||||||
|
@ -140,18 +122,12 @@ is($node->psql('regression_invalid_interrupt', ''),
|
||||||
|
|
||||||
# To properly drop the database, we need to release the lock previously preventing
|
# To properly drop the database, we need to release the lock previously preventing
|
||||||
# doing so.
|
# doing so.
|
||||||
$bgpsql_in .= qq(
|
ok($bgpsql->query_safe(qq(ROLLBACK PREPARED 'lock_tblspc')),
|
||||||
ROLLBACK PREPARED 'lock_tblspc';
|
|
||||||
\\echo ROLLBACK PREPARED
|
|
||||||
);
|
|
||||||
ok(pump_until($bgpsql, $bgpsql_timer, \$bgpsql_out, qr/ROLLBACK PREPARED/),
|
|
||||||
"unblock DROP DATABASE");
|
"unblock DROP DATABASE");
|
||||||
$bgpsql_out = '';
|
|
||||||
|
|
||||||
is($node->psql('postgres', "DROP DATABASE regression_invalid_interrupt"),
|
ok($bgpsql->query(qq(DROP DATABASE regression_invalid_interrupt)),
|
||||||
0, "DROP DATABASE invalid_interrupt");
|
"DROP DATABASE invalid_interrupt");
|
||||||
|
|
||||||
$bgpsql_in .= "\\q\n";
|
$bgpsql->quit();
|
||||||
$bgpsql->finish();
|
|
||||||
|
|
||||||
done_testing();
|
done_testing();
|
||||||
|
|
|
@ -50,21 +50,15 @@ my $result =
|
||||||
is($result, qq(2|2|2), 'check initial data was copied to subscriber');
|
is($result, qq(2|2|2), 'check initial data was copied to subscriber');
|
||||||
|
|
||||||
# Interleave a pair of transactions, each exceeding the 64kB limit.
|
# Interleave a pair of transactions, each exceeding the 64kB limit.
|
||||||
my $in = '';
|
my $h = $node_publisher->background_psql('postgres',
|
||||||
my $out = '';
|
|
||||||
|
|
||||||
my $timer = IPC::Run::timeout($PostgreSQL::Test::Utils::timeout_default);
|
|
||||||
|
|
||||||
my $h = $node_publisher->background_psql('postgres', \$in, \$out, $timer,
|
|
||||||
on_error_stop => 0);
|
on_error_stop => 0);
|
||||||
|
|
||||||
$in .= q{
|
$h->query_safe(q{
|
||||||
BEGIN;
|
BEGIN;
|
||||||
INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(3, 5000) s(i);
|
INSERT INTO test_tab SELECT i, md5(i::text) FROM generate_series(3, 5000) s(i);
|
||||||
UPDATE test_tab SET b = md5(b) WHERE mod(a,2) = 0;
|
UPDATE test_tab SET b = md5(b) WHERE mod(a,2) = 0;
|
||||||
DELETE FROM test_tab WHERE mod(a,3) = 0;
|
DELETE FROM test_tab WHERE mod(a,3) = 0;
|
||||||
};
|
});
|
||||||
$h->pump_nb;
|
|
||||||
|
|
||||||
$node_publisher->safe_psql(
|
$node_publisher->safe_psql(
|
||||||
'postgres', q{
|
'postgres', q{
|
||||||
|
@ -74,11 +68,9 @@ DELETE FROM test_tab WHERE a > 5000;
|
||||||
COMMIT;
|
COMMIT;
|
||||||
});
|
});
|
||||||
|
|
||||||
$in .= q{
|
$h->query_safe('COMMIT');
|
||||||
COMMIT;
|
# errors make the next test fail, so ignore them here
|
||||||
\q
|
$h->quit;
|
||||||
};
|
|
||||||
$h->finish; # errors make the next test fail, so ignore them here
|
|
||||||
|
|
||||||
$node_publisher->wait_for_catchup($appname);
|
$node_publisher->wait_for_catchup($appname);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue