Finally, I added new TAP test library PsqlSession. At Tue, 18 Jul 2017 18:12:13 +0900 (Tokyo Standard Time), Kyotaro HORIGUCHI <horiguchi.kyot...@lab.ntt.co.jp> wrote in <20170718.181213.206979369.horiguchi.kyot...@lab.ntt.co.jp> > > * How about some regression test cases? You couldn't really exercise > > cross-session invalidation easily, but I don't think you need to. > > Ha Ha. You got me. I will add some test cases for this in the > next version. Thanks.
Here it is. First I tried this using ordinary regression framework but the effect of this patch is shown only in log and it contains variable parts so I gave up it before trying more complex way. Next I tried existing TAP test but this test needs continuous session to achieve alternating operation on two sessions but PostgresNode::psql doesn't offer such a functionality. Finally, I added a new TAP test library PsqlSession. It offers interactive psql sessions. Then added a simple test to postgres_fdw using it. The first patch is the PsqlSession.pm and the second is the new test for postgres_fdw. - The current PsqlSession is quite fragile but seems working enough for this usage at the present. - I'm afraid this might not work on Windows according to the manpage of IPC::Run, but I haven't confirmed yet. http://search.cpan.org/~toddr/IPC-Run-0.96/lib/IPC/Run.pm#Win32_LIMITATIONS Any comment or suggestions are welcome. regards, -- Kyotaro Horiguchi NTT Open Source Software Center
>From fdb5cbab3375d9d2e4da078cf6ee7eaf7de5c8fd Mon Sep 17 00:00:00 2001 From: Kyotaro Horiguchi <horiguchi.kyot...@lab.ntt.co.jp> Date: Thu, 20 Jul 2017 14:56:51 +0900 Subject: [PATCH 1/2] Add new TAP test library PsqlSession.pm PostgreNode::psql makes temporary session to run commands so it is not usable when more interactive operation on a continued session. This library offers continuous sessions feature that can execute multiple sql commands separately. --- src/test/perl/PsqlSession.pm | 341 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100644 src/test/perl/PsqlSession.pm diff --git a/src/test/perl/PsqlSession.pm b/src/test/perl/PsqlSession.pm new file mode 100644 index 0000000..d69fd14 --- /dev/null +++ b/src/test/perl/PsqlSession.pm @@ -0,0 +1,341 @@ + +=pod + +=head1 NAME + +PsqlSession - class representing PostgreSQL psql instance + +=head1 SYNOPSIS + + use PsqlSession; + + my $session = get_new_session('session1', $server); + + to connect to a PostgreNode as $server, or + + my $session = get_new_session('session1', 'localhost', '5432', 'postgres'); + + to specify each options explicitly. + + # Connect to the server + $session->open(); + + # Execute an SQL query + $ret = $session->execsql('SELECT now();'); + + Returns a pair of output of stdout, and stderr in array context. + + ($out, $err) = $session->execsql('SELECT now();'); + + $session->execsql_multi('SELECT 1;', 'SELECT now();'); + + is just a shortcut of writing many execsqls. + + # End the session + $session->close(); + +=head1 DESCRIPTION + +PsqlSession contains a set of routines able to work on a psql session, +allowing to connect, send a command and receive the result and close. + +The IPC::Run module is required. + +=cut + +package PsqlSession; + +use strict; +use warnings; + +use Exporter 'import'; +use Test::More; +use TestLib (); +use Scalar::Util qw(blessed); + +our @EXPORT = qw( + get_new_session +); + + +=pod + +=head1 METHODS + +=over + +=item PsqlSession::new($class, $name, $pghost, $pgport, $dbname) + +Create a new PsqlSession instance. Does not connect. + +You should generally prefer to use get_new_session() instead since it +takes care of finding host name, port number or database name. + +=cut + +sub new +{ + my ($class, $name, $pghost, $pgport, $dbname) = @_; + + my $self = { + _name => $name, + _host => $pghost, + _port => $pgport, + _dbname => $dbname }; + + bless $self, $class; + +# $self->dump_info; + + return $self; +} + +=pod + +=item $session->name() + +The name assigned to the session at creation time. + +=cut + +sub name +{ + return $_[0]->{_name}; +} + +=pod + +=item $session->host() + +Return the host (like PGHOST) for this instance. May be a UNIX socket path. + +=cut + +sub host +{ + return $_[0]->{_host}; +} + +=pod + +=item $session->port() + +Get the port number connects to. This won't necessarily be a TCP port +open on the local host since we prefer to use unix sockets if +possible. + +=cut + +sub port +{ + return $_[0]->{_port}; +} + +=pod + +=item $session->dbname() + +Get the database name this session connects to. + +=cut + +sub dbname +{ + return $_[0]->{_dbname}; +} + +=pod + +=item $session->errstate() + +Get the error state of this session. 0 means no error and 1 means +error. This value is reset at the starting of every execution of an +SQL query. + +=cut + +sub errstate +{ + return $_[0]->{_errstate}; +} + +=pod + +=item $session->open() + +Open this session. + +=cut + +sub open +{ + my ($self) = @_; + + # Create anonymous scalar references to be passed to IPC::Run::start + my $in = do {\my $anon}; + my $out = do {\my $anon}; + my $err = do {\my $anon}; + + # create and store the harness + $self->{_run} = IPC::Run::start + ["psql", $self->dbname, "-p" ,$self->port, "-h", $self->host, "-n"], + $in, $out, $err, IPC::Run::timeout(10); + + $self->{_in} = $in; + $self->{_out} = $out; + $self->{_err} = $err; + $self->{_errstate} = 0; +} + +=pod + +=item $session->clearerr() + +Clear error status. + +=cut + +sub clearerr +{ + $_[0]->errstate = 0; +} + +=pod + +=item $session->close() + +Close this session. This session can be relaunched by open(). + +=cut + +sub close +{ + my ($self) = @_; + + if (defined $self->{_run}) + { + IPC::Run::finish($self->{_run}); + delete $self->{_run}; + } +} + +=pod + +=item $session->execsql($sqlstr) + +Execute sql commands in $sqlstr then returns the +result. Multistatement is allowed. + +In scalar context, this function returns a string that contains the +result of the query. In array context, returns an array consists of +the output of stdout and stderr. + +For implment reasons, this function adds a sentinel query that gives a +end marker for reading the result at the end of the query. The stderr +may contain the log for the sentinel query on certain setting, such as +log_min_duration_statement = 0. + +If the stderr contained the string 'ERROR:', this session is marked as +error. This state can be checked by $session->errstate(); + +=cut + +sub execsql +{ + my ($self, $cmd) = @_; + my $run = $self->{_run}; + + die if (!defined $run); + ${$self->{_out}} = ''; + ${$self->{_err}} = ''; + + # add a query to receive an end marker + $cmd =~ s/[;\n]+$//; + ${$self->{_in}} .= $cmd.";select '###END###' as end;\n"; + + # wait for the end marker or an error + $self->{_run}->pump until + (${$self->{_out}} =~ /###END###\n\(1 row\)\n/ || + ${$self->{_err}} =~ /ERROR:/); + + # set error state + $self->{_errstate} = 1 if (${$self->{_err}} =~ /ERROR:/); + + # remove useless result + my $out = ${$self->{_out}}; + $out =~ s/ *end *\n-+\n *###END### *\n\(1 row\)\n\n//g; + + return wantarray ? ($out, ${$self->{_err}}) : $out; +} + +=pod + +=item $session->execsql_multi("query", "query",...) + +Run multiple queries. This is just a convenient function using instead +of a series of execsql(). Returns concatenated output of each +query. Returns the output of stdout in scalar context and a pair of +stdout and stderr in array context. + +If any query fails, this function returns immediately. + +=cut + +sub execsql_multi +{ + my ($self) = shift(@_); + my ($retout, $reterr, $out, $err); + + foreach my $cmd (@_) + { + ($out, $err) = $self->execsql($cmd); + $retout .= $out; + $reterr .= $err; + last if ($self->errstate); + } + + return wantarray ? ($retout, $reterr) : $retout; +} + +=pod + +=item get_new_sesion($name, $host, $port, $dbname) or + get_new_sesion($name, $PostgreNode) + +Build a new session object to connect to the specified +server. Standalone function that's automatically imported. + +In the first form, returns a session according to the parameters. In +the second form, returns a session connects to the node. + +=cut + + +sub get_new_session +{ + my ($name, $host_or_ref, $pgport, $dbname) = @_; + my $pghost = $host_or_ref; + + if (blessed($host_or_ref) && $host_or_ref->isa('PostgresNode')) + { + $pghost = $host_or_ref->host; + $pgport = $host_or_ref->port; + } + + # Fill in default values + $pghost = 'localhost' if (! defined $pghost); + $pgport = 5432 if (! defined $pgport); + $dbname = 'postgres' if (! defined $dbname); + + my $session = new PsqlSession($name, $pghost, $pgport, $dbname); + + return $session; +} + +=pod + +=back + +=cut + +1; -- 2.9.2
>From c47a3b348c5818e341668b074582eef6b23376ef Mon Sep 17 00:00:00 2001 From: Kyotaro Horiguchi <horiguchi.kyot...@lab.ntt.co.jp> Date: Thu, 20 Jul 2017 15:02:12 +0900 Subject: [PATCH 2/2] Add test for reconnection feature This patch adds the regression test for the session reconnection feature of postgres_fdw. make prove-check runs the test. --- contrib/postgres_fdw/Makefile | 3 ++ contrib/postgres_fdw/t/001_reconnection.pl | 65 ++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 contrib/postgres_fdw/t/001_reconnection.pl diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile index 3543312..1be5c9f 100644 --- a/contrib/postgres_fdw/Makefile +++ b/contrib/postgres_fdw/Makefile @@ -23,3 +23,6 @@ top_builddir = ../.. include $(top_builddir)/src/Makefile.global include $(top_srcdir)/contrib/contrib-global.mk endif + +prove-check: + $(prove_check) diff --git a/contrib/postgres_fdw/t/001_reconnection.pl b/contrib/postgres_fdw/t/001_reconnection.pl new file mode 100644 index 0000000..b93725d --- /dev/null +++ b/contrib/postgres_fdw/t/001_reconnection.pl @@ -0,0 +1,65 @@ +# Minimal test testing reconnection +use strict; +use warnings; +use PostgresNode; +use PsqlSession; +use TestLib; +use Test::More tests => 6; + +# start a server +my $server = get_new_node('server'); +$server->init(); +$server->start; +my $session1 = get_new_session('session1', $server); +my $session2 = get_new_session('session2', $server); +$session1->open; +$session2->open; + +my $ret = $session1->execsql("SELECT current_database()"); +ok($ret =~ /^.*\n-.*-\n *(\S+) *\n/, + 'retrieving connection setting'); +my $dbname = $1; +my $port = $server->port; + +$session1->execsql_multi( + ("CREATE EXTENSION postgres_fdw;", + "CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw OPTIONS (dbname \'$dbname\', port \'$port\');", + "CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;", + "CREATE TABLE lt1 (c1 int);", + "INSERT INTO lt1 VALUES (1);", + "CREATE FOREIGN TABLE ft1 (c1 int) SERVER loopback OPTIONS (table_name 'lt1');", + "SET client_min_messages to DEBUG3;")); +ok($session1->errstate == 0, 'setting up'); + +$session1->execsql("BEGIN;"); + +my ($stout, $sterr) = $session1->execsql("SELECT c1 FROM ft1 LIMIT 1;"); +ok($sterr =~ /DEBUG: *new postgres_fdw connection 0x[[:xdigit:]]+/, + "creating new connection"); + +$session2->execsql_multi( + "ALTER SERVER loopback OPTIONS (ADD host 'hoge')", + "ALTER SERVER loopback OPTIONS (DROP host)"); + +($stout, $sterr) = $session1->execsql("SELECT c1 FROM ft1 LIMIT 1;"); +ok($sterr !~ /DEBUG: *closing connection 0x[[:xdigit:]]+ for option changes to take effect/, + 'no disconnection within a transaction'); + +$session1->execsql("COMMIT;"); + +$session2->execsql_multi( + "ALTER USER MAPPING ft1 OPTIONS (host 'hoge')", + "ALTER SERVER loopback OPTIONS (DROP host)"); + + +($stout, $sterr) = $session1->execsql("SELECT c1 FROM ft1 LIMIT 1;"); +ok($sterr =~ /DEBUG: *closing connection 0x[[:xdigit:]]+ for option changes to take effect\nDEBUG: *new postgres_fdw connection 0x[[:xdigit:]]+/, + 'reconnection by option change outside a transactin'); + +($stout, $sterr) = $session1->execsql("SELECT c1 FROM ft1 LIMIT 1;"); +ok($sterr !~ /DEBUG: *closing connection 0x[[:xdigit:]]+ for option changes to take effect/, + 'no disconnection without option change'); + +$session1->close; +$session2->close; +$server->stop; -- 2.9.2
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers