Extend the abilities of libpq's target_session_attrs parameter.

In addition to the existing options of "any" and "read-write", we
now support "read-only", "primary", "standby", and "prefer-standby".
"read-write" retains its previous meaning of "transactions are
read-write by default", and "read-only" inverts that.  The other
three modes test specifically for hot-standby status, which is not
quite the same thing.  (Setting default_transaction_read_only on
a primary server renders it read-only to this logic, but not a
standby.)

Furthermore, if talking to a v14 or later server, no extra network
round trip is needed to detect the session's status; the GUC_REPORT
variables delivered by the server are enough.  When talking to an
older server, a SHOW or SELECT query is issued to detect session
read-only-ness or server hot-standby state, as needed.

Haribabu Kommi, Greg Nancarrow, Vignesh C, Tom Lane; reviewed at
various times by Laurenz Albe, Takayuki Tsunakawa, Peter Smith.

Discussion: https://postgr.es/m/CAF3+xM+8-ztOkaV9gHiJ3wfgENTq97QcjXQt+rbFQ6F7oNzt9A@mail.gmail.com
This commit is contained in:
Tom Lane 2021-03-02 20:17:45 -05:00
parent 57e6db706e
commit ee28cacf61
6 changed files with 463 additions and 121 deletions

View File

@ -1877,18 +1877,75 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>target_session_attrs</literal></term>
<listitem>
<para>
If this parameter is set to <literal>read-write</literal>, only a
connection in which read-write transactions are accepted by default
is considered acceptable. The query
<literal>SHOW transaction_read_only</literal> will be sent upon any
successful connection; if it returns <literal>on</literal>, the connection
will be closed. If multiple hosts were specified in the connection
string, any remaining servers will be tried just as if the connection
attempt had failed. The default value of this parameter,
<literal>any</literal>, regards all connections as acceptable.
</para>
This option determines whether the session must have certain
properties to be acceptable. It's typically used in combination
with multiple host names to select the first acceptable alternative
among several hosts. There are six modes:
<variablelist>
<varlistentry>
<term><literal>any</literal> (default)</term>
<listitem>
<para>
any successful connection is acceptable
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>read-write</literal></term>
<listitem>
<para>
session must accept read-write transactions by default (that
is, the server must not be in hot standby mode and
the <varname>default_transaction_read_only</varname> parameter
must be <literal>off</literal>)
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>read-only</literal></term>
<listitem>
<para>
session must not accept read-write transactions by default (the
converse)
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>primary</literal></term>
<listitem>
<para>
server must not be in hot standby mode
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>standby</literal></term>
<listitem>
<para>
server must be in hot standby mode
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>prefer-standby</literal></term>
<listitem>
<para>
first try to find a standby server, but if none of the listed
hosts is a standby server, try again in <literal>all</literal>
mode
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
</listitem>
</varlistentry>
</varlistentry>
</variablelist>
</para>
</sect2>

View File

@ -356,7 +356,7 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
{"target_session_attrs", "PGTARGETSESSIONATTRS",
DefaultTargetSessionAttrs, NULL,
"Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
"Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
offsetof(struct pg_conn, target_session_attrs)},
/* Terminating entry --- MUST BE LAST */
@ -583,6 +583,8 @@ pqDropServerData(PGconn *conn)
conn->pstatus = NULL;
conn->client_encoding = PG_SQL_ASCII;
conn->std_strings = false;
conn->default_transaction_read_only = PG_BOOL_UNKNOWN;
conn->in_hot_standby = PG_BOOL_UNKNOWN;
conn->sversion = 0;
/* Drop large-object lookup data */
@ -1388,6 +1390,36 @@ connectOptions2(PGconn *conn)
goto oom_error;
}
/*
* validate target_session_attrs option, and set target_server_type
*/
if (conn->target_session_attrs)
{
if (strcmp(conn->target_session_attrs, "any") == 0)
conn->target_server_type = SERVER_TYPE_ANY;
else if (strcmp(conn->target_session_attrs, "read-write") == 0)
conn->target_server_type = SERVER_TYPE_READ_WRITE;
else if (strcmp(conn->target_session_attrs, "read-only") == 0)
conn->target_server_type = SERVER_TYPE_READ_ONLY;
else if (strcmp(conn->target_session_attrs, "primary") == 0)
conn->target_server_type = SERVER_TYPE_PRIMARY;
else if (strcmp(conn->target_session_attrs, "standby") == 0)
conn->target_server_type = SERVER_TYPE_STANDBY;
else if (strcmp(conn->target_session_attrs, "prefer-standby") == 0)
conn->target_server_type = SERVER_TYPE_PREFER_STANDBY;
else
{
conn->status = CONNECTION_BAD;
appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("invalid %s value: \"%s\"\n"),
"target_session_attrs",
conn->target_session_attrs);
return false;
}
}
else
conn->target_server_type = SERVER_TYPE_ANY;
/*
* Resolve special "auto" client_encoding from the locale
*/
@ -1400,23 +1432,6 @@ connectOptions2(PGconn *conn)
goto oom_error;
}
/*
* Validate target_session_attrs option.
*/
if (conn->target_session_attrs)
{
if (strcmp(conn->target_session_attrs, "any") != 0
&& strcmp(conn->target_session_attrs, "read-write") != 0)
{
conn->status = CONNECTION_BAD;
appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("invalid %s value: \"%s\"\n"),
"target_settion_attrs",
conn->target_session_attrs);
return false;
}
}
/*
* Only if we get this far is it appropriate to try to connect. (We need a
* state flag, rather than just the boolean result of this function, in
@ -2057,6 +2072,10 @@ connectDBStart(PGconn *conn)
conn->try_next_host = true;
conn->status = CONNECTION_NEEDED;
/* Also reset the target_server_type state if needed */
if (conn->target_server_type == SERVER_TYPE_PREFER_STANDBY_PASS2)
conn->target_server_type = SERVER_TYPE_PREFER_STANDBY;
/*
* The code for processing CONNECTION_NEEDED state is in PQconnectPoll(),
* so that it can easily be re-executed if needed again during the
@ -2250,6 +2269,9 @@ PQconnectPoll(PGconn *conn)
/* These are reading states */
case CONNECTION_AWAITING_RESPONSE:
case CONNECTION_AUTH_OK:
case CONNECTION_CHECK_WRITABLE:
case CONNECTION_CONSUME:
case CONNECTION_CHECK_STANDBY:
{
/* Load waiting data */
int n = pqReadData(conn);
@ -2274,9 +2296,8 @@ PQconnectPoll(PGconn *conn)
/* Special cases: proceed without waiting. */
case CONNECTION_SSL_STARTUP:
case CONNECTION_NEEDED:
case CONNECTION_CHECK_WRITABLE:
case CONNECTION_CONSUME:
case CONNECTION_GSS_STARTUP:
case CONNECTION_CHECK_TARGET:
break;
default:
@ -2311,15 +2332,28 @@ keep_going: /* We will come back to here until there is
int ret;
char portstr[MAXPGPATH];
if (conn->whichhost + 1 >= conn->nconnhost)
if (conn->whichhost + 1 < conn->nconnhost)
conn->whichhost++;
else
{
/*
* Oops, no more hosts. An appropriate error message is already
* set up, so just set the right status.
* Oops, no more hosts.
*
* If we are trying to connect in "prefer-standby" mode, then drop
* the standby requirement and start over.
*
* Otherwise, an appropriate error message is already set up, so
* we just need to set the right status.
*/
goto error_return;
if (conn->target_server_type == SERVER_TYPE_PREFER_STANDBY &&
conn->nconnhost > 0)
{
conn->target_server_type = SERVER_TYPE_PREFER_STANDBY_PASS2;
conn->whichhost = 0;
}
else
goto error_return;
}
conn->whichhost++;
/* Drop any address info for previous host */
release_conn_addrinfo(conn);
@ -3550,28 +3584,131 @@ keep_going: /* We will come back to here until there is
case CONNECTION_CHECK_TARGET:
{
/*
* If a read-write connection is required, see if we have one.
*
* Servers before 7.4 lack the transaction_read_only GUC, but
* by the same token they don't have any read-only mode, so we
* may just skip the test in that case.
* If a read-write, read-only, primary, or standby connection
* is required, see if we have one.
*/
if (conn->sversion >= 70400 &&
conn->target_session_attrs != NULL &&
strcmp(conn->target_session_attrs, "read-write") == 0)
if (conn->target_server_type == SERVER_TYPE_READ_WRITE ||
conn->target_server_type == SERVER_TYPE_READ_ONLY)
{
bool read_only_server;
/*
* If the server didn't report
* "default_transaction_read_only" or "in_hot_standby" at
* startup, we must determine its state by sending the
* query "SHOW transaction_read_only". Servers before 7.4
* lack the transaction_read_only GUC, but by the same
* token they don't have any read-only mode, so we may
* just assume the results.
*/
if (conn->sversion < 70400)
{
conn->default_transaction_read_only = PG_BOOL_NO;
conn->in_hot_standby = PG_BOOL_NO;
}
if (conn->default_transaction_read_only == PG_BOOL_UNKNOWN ||
conn->in_hot_standby == PG_BOOL_UNKNOWN)
{
/*
* We use PQsendQueryContinue so that
* conn->errorMessage does not get cleared. We need
* to preserve any error messages related to previous
* hosts we have tried and failed to connect to.
*/
conn->status = CONNECTION_OK;
if (!PQsendQueryContinue(conn,
"SHOW transaction_read_only"))
goto error_return;
/* We'll return to this state when we have the answer */
conn->status = CONNECTION_CHECK_WRITABLE;
return PGRES_POLLING_READING;
}
/* OK, we can make the test */
read_only_server =
(conn->default_transaction_read_only == PG_BOOL_YES ||
conn->in_hot_standby == PG_BOOL_YES);
if ((conn->target_server_type == SERVER_TYPE_READ_WRITE) ?
read_only_server : !read_only_server)
{
/* Wrong server state, reject and try the next host */
if (conn->target_server_type == SERVER_TYPE_READ_WRITE)
appendPQExpBufferStr(&conn->errorMessage,
libpq_gettext("session is read-only\n"));
else
appendPQExpBufferStr(&conn->errorMessage,
libpq_gettext("session is not read-only\n"));
/* Close connection politely. */
conn->status = CONNECTION_OK;
sendTerminateConn(conn);
/*
* Try next host if any, but we don't want to consider
* additional addresses for this host.
*/
conn->try_next_host = true;
goto keep_going;
}
}
else if (conn->target_server_type == SERVER_TYPE_PRIMARY ||
conn->target_server_type == SERVER_TYPE_STANDBY ||
conn->target_server_type == SERVER_TYPE_PREFER_STANDBY)
{
/*
* We use PQsendQueryContinue so that conn->errorMessage
* does not get cleared. We need to preserve any error
* messages related to previous hosts we have tried and
* failed to connect to.
* If the server didn't report "in_hot_standby" at
* startup, we must determine its state by sending the
* query "SELECT pg_catalog.pg_is_in_recovery()". Servers
* before 9.0 don't have that function, but by the same
* token they don't have any standby mode, so we may just
* assume the result.
*/
conn->status = CONNECTION_OK;
if (!PQsendQueryContinue(conn,
"SHOW transaction_read_only"))
goto error_return;
conn->status = CONNECTION_CHECK_WRITABLE;
return PGRES_POLLING_READING;
if (conn->sversion < 90000)
conn->in_hot_standby = PG_BOOL_NO;
if (conn->in_hot_standby == PG_BOOL_UNKNOWN)
{
/*
* We use PQsendQueryContinue so that
* conn->errorMessage does not get cleared. We need
* to preserve any error messages related to previous
* hosts we have tried and failed to connect to.
*/
conn->status = CONNECTION_OK;
if (!PQsendQueryContinue(conn,
"SELECT pg_catalog.pg_is_in_recovery()"))
goto error_return;
/* We'll return to this state when we have the answer */
conn->status = CONNECTION_CHECK_STANDBY;
return PGRES_POLLING_READING;
}
/* OK, we can make the test */
if ((conn->target_server_type == SERVER_TYPE_PRIMARY) ?
(conn->in_hot_standby == PG_BOOL_YES) :
(conn->in_hot_standby == PG_BOOL_NO))
{
/* Wrong server state, reject and try the next host */
if (conn->target_server_type == SERVER_TYPE_PRIMARY)
appendPQExpBufferStr(&conn->errorMessage,
libpq_gettext("server is in hot standby mode\n"));
else
appendPQExpBufferStr(&conn->errorMessage,
libpq_gettext("server is not in hot standby mode\n"));
/* Close connection politely. */
conn->status = CONNECTION_OK;
sendTerminateConn(conn);
/*
* Try next host if any, but we don't want to consider
* additional addresses for this host.
*/
conn->try_next_host = true;
goto keep_going;
}
}
/* We can release the address list now. */
@ -3617,6 +3754,14 @@ keep_going: /* We will come back to here until there is
case CONNECTION_CONSUME:
{
/*
* This state just makes sure the connection is idle after
* we've obtained the result of a SHOW or SELECT query. Once
* we're clear, return to CONNECTION_CHECK_TARGET state to
* decide what to do next. We must transiently set status =
* CONNECTION_OK in order to use the result-consuming
* subroutines.
*/
conn->status = CONNECTION_OK;
if (!PQconsumeInput(conn))
goto error_return;
@ -3627,26 +3772,26 @@ keep_going: /* We will come back to here until there is
return PGRES_POLLING_READING;
}
/*
* Call PQgetResult() again to consume NULL result.
*/
/* Call PQgetResult() again until we get a NULL result */
res = PQgetResult(conn);
if (res != NULL)
{
PQclear(res);
conn->status = CONNECTION_CONSUME;
goto keep_going;
return PGRES_POLLING_READING;
}
/* We can release the address list now. */
release_conn_addrinfo(conn);
/* We are open for business! */
conn->status = CONNECTION_OK;
return PGRES_POLLING_OK;
conn->status = CONNECTION_CHECK_TARGET;
goto keep_going;
}
case CONNECTION_CHECK_WRITABLE:
{
/*
* Waiting for result of "SHOW transaction_read_only". We
* must transiently set status = CONNECTION_OK in order to use
* the result-consuming subroutines.
*/
conn->status = CONNECTION_OK;
if (!PQconsumeInput(conn))
goto error_return;
@ -3658,61 +3803,102 @@ keep_going: /* We will come back to here until there is
}
res = PQgetResult(conn);
if (res && (PQresultStatus(res) == PGRES_TUPLES_OK) &&
if (res && PQresultStatus(res) == PGRES_TUPLES_OK &&
PQntuples(res) == 1)
{
char *val;
val = PQgetvalue(res, 0, 0);
if (strncmp(val, "on", 2) == 0)
{
/* Not writable; fail this connection. */
PQclear(res);
/* Append error report to conn->errorMessage. */
appendPQExpBufferStr(&conn->errorMessage,
libpq_gettext("session is read-only\n"));
/* Close connection politely. */
conn->status = CONNECTION_OK;
sendTerminateConn(conn);
/*
* Try next host if any, but we don't want to consider
* additional addresses for this host.
*/
conn->try_next_host = true;
goto keep_going;
}
/* Session is read-write, so we're good. */
PQclear(res);
char *val = PQgetvalue(res, 0, 0);
/*
* Finish reading any remaining messages before being
* considered as ready.
* "transaction_read_only = on" proves that at least one
* of default_transaction_read_only and in_hot_standby is
* on, but we don't actually know which. We don't care
* though for the purpose of identifying a read-only
* session, so satisfy the CONNECTION_CHECK_TARGET code by
* claiming they are both on. On the other hand, if it's
* a read-write session, they are certainly both off.
*/
if (strncmp(val, "on", 2) == 0)
{
conn->default_transaction_read_only = PG_BOOL_YES;
conn->in_hot_standby = PG_BOOL_YES;
}
else
{
conn->default_transaction_read_only = PG_BOOL_NO;
conn->in_hot_standby = PG_BOOL_NO;
}
PQclear(res);
/* Finish reading messages before continuing */
conn->status = CONNECTION_CONSUME;
goto keep_going;
}
/*
* Something went wrong with "SHOW transaction_read_only". We
* should try next addresses.
*/
/* Something went wrong with "SHOW transaction_read_only". */
if (res)
PQclear(res);
/* Append error report to conn->errorMessage. */
appendPQExpBufferStr(&conn->errorMessage,
libpq_gettext("test \"SHOW transaction_read_only\" failed\n"));
libpq_gettext("\"SHOW transaction_read_only\" failed\n"));
/* Close connection politely. */
conn->status = CONNECTION_OK;
sendTerminateConn(conn);
/* Try next address */
conn->try_next_addr = true;
/* Try next host. */
conn->try_next_host = true;
goto keep_going;
}
case CONNECTION_CHECK_STANDBY:
{
/*
* Waiting for result of "SELECT pg_is_in_recovery()". We
* must transiently set status = CONNECTION_OK in order to use
* the result-consuming subroutines.
*/
conn->status = CONNECTION_OK;
if (!PQconsumeInput(conn))
goto error_return;
if (PQisBusy(conn))
{
conn->status = CONNECTION_CHECK_STANDBY;
return PGRES_POLLING_READING;
}
res = PQgetResult(conn);
if (res && PQresultStatus(res) == PGRES_TUPLES_OK &&
PQntuples(res) == 1)
{
char *val = PQgetvalue(res, 0, 0);
if (strncmp(val, "t", 1) == 0)
conn->in_hot_standby = PG_BOOL_YES;
else
conn->in_hot_standby = PG_BOOL_NO;
PQclear(res);
/* Finish reading messages before continuing */
conn->status = CONNECTION_CONSUME;
goto keep_going;
}
/* Something went wrong with "SELECT pg_is_in_recovery()". */
if (res)
PQclear(res);
/* Append error report to conn->errorMessage. */
appendPQExpBufferStr(&conn->errorMessage,
libpq_gettext("\"SELECT pg_is_in_recovery()\" failed\n"));
/* Close connection politely. */
conn->status = CONNECTION_OK;
sendTerminateConn(conn);
/* Try next host. */
conn->try_next_host = true;
goto keep_going;
}
@ -3859,6 +4045,8 @@ makeEmptyPGconn(void)
conn->setenv_state = SETENV_STATE_IDLE;
conn->client_encoding = PG_SQL_ASCII;
conn->std_strings = false; /* unless server says differently */
conn->default_transaction_read_only = PG_BOOL_UNKNOWN;
conn->in_hot_standby = PG_BOOL_UNKNOWN;
conn->verbosity = PQERRORS_DEFAULT;
conn->show_context = PQSHOW_CONTEXT_ERRORS;
conn->sock = PGINVALID_SOCKET;

View File

@ -1008,11 +1008,11 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
}
/*
* Special hacks: remember client_encoding and
* standard_conforming_strings, and convert server version to a numeric
* form. We keep the first two of these in static variables as well, so
* that PQescapeString and PQescapeBytea can behave somewhat sanely (at
* least in single-connection-using programs).
* Save values of settings that are of interest to libpq in fields of the
* PGconn object. We keep client_encoding and standard_conforming_strings
* in static variables as well, so that PQescapeString and PQescapeBytea
* can behave somewhat sanely (at least in single-connection-using
* programs).
*/
if (strcmp(name, "client_encoding") == 0)
{
@ -1029,6 +1029,7 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
}
else if (strcmp(name, "server_version") == 0)
{
/* We convert the server version to numeric form. */
int cnt;
int vmaj,
vmin,
@ -1062,6 +1063,16 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
else
conn->sversion = 0; /* unknown */
}
else if (strcmp(name, "default_transaction_read_only") == 0)
{
conn->default_transaction_read_only =
(strcmp(value, "on") == 0) ? PG_BOOL_YES : PG_BOOL_NO;
}
else if (strcmp(name, "in_hot_standby") == 0)
{
conn->in_hot_standby =
(strcmp(value, "on") == 0) ? PG_BOOL_YES : PG_BOOL_NO;
}
}

View File

@ -63,12 +63,11 @@ typedef enum
CONNECTION_SETENV, /* Negotiating environment. */
CONNECTION_SSL_STARTUP, /* Negotiating SSL. */
CONNECTION_NEEDED, /* Internal state: connect() needed */
CONNECTION_CHECK_WRITABLE, /* Check if we could make a writable
* connection. */
CONNECTION_CONSUME, /* Wait for any pending message and consume
* them. */
CONNECTION_CHECK_WRITABLE, /* Checking if session is read-write. */
CONNECTION_CONSUME, /* Consuming any extra messages. */
CONNECTION_GSS_STARTUP, /* Negotiating GSSAPI. */
CONNECTION_CHECK_TARGET /* Check if we have a proper target connection */
CONNECTION_CHECK_TARGET, /* Checking target server properties. */
CONNECTION_CHECK_STANDBY /* Checking if server is in standby mode. */
} ConnStatusType;
typedef enum

View File

@ -232,6 +232,26 @@ typedef enum
PGQUERY_DESCRIBE /* Describe Statement or Portal */
} PGQueryClass;
/* Target server type (decoded value of target_session_attrs) */
typedef enum
{
SERVER_TYPE_ANY = 0, /* Any server (default) */
SERVER_TYPE_READ_WRITE, /* Read-write server */
SERVER_TYPE_READ_ONLY, /* Read-only server */
SERVER_TYPE_PRIMARY, /* Primary server */
SERVER_TYPE_STANDBY, /* Standby server */
SERVER_TYPE_PREFER_STANDBY, /* Prefer standby server */
SERVER_TYPE_PREFER_STANDBY_PASS2 /* second pass - behaves same as ANY */
} PGTargetServerType;
/* Boolean value plus a not-known state, for GUCs we might have to fetch */
typedef enum
{
PG_BOOL_UNKNOWN = 0, /* Currently unknown */
PG_BOOL_YES, /* Yes (true) */
PG_BOOL_NO /* No (false) */
} PGTernaryBool;
/* PGSetenvStatusType defines the state of the pqSetenv state machine */
/* (this is used only for 2.0-protocol connections) */
@ -370,9 +390,7 @@ struct pg_conn
* "sspi") */
char *ssl_min_protocol_version; /* minimum TLS protocol version */
char *ssl_max_protocol_version; /* maximum TLS protocol version */
/* Type of connection to make. Possible values: any, read-write. */
char *target_session_attrs;
char *target_session_attrs; /* desired session properties */
/* Optional file to write trace info to */
FILE *Pfdebug;
@ -422,6 +440,7 @@ struct pg_conn
char *write_err_msg; /* write error message, or NULL if OOM */
/* Transient state needed while establishing connection */
PGTargetServerType target_server_type; /* desired session properties */
bool try_next_addr; /* time to advance to next address/host? */
bool try_next_host; /* time to advance to next connhost[]? */
struct addrinfo *addrlist; /* list of addresses for current connhost */
@ -437,6 +456,8 @@ struct pg_conn
pgParameterStatus *pstatus; /* ParameterStatus data */
int client_encoding; /* encoding id */
bool std_strings; /* standard_conforming_strings */
PGTernaryBool default_transaction_read_only; /* default_transaction_read_only */
PGTernaryBool in_hot_standby; /* in_hot_standby */
PGVerbosity verbosity; /* error/notice message verbosity */
PGContextVisibility show_context; /* whether to show CONTEXT field */
PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */

View File

@ -3,7 +3,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
use Test::More tests => 36;
use Test::More tests => 49;
# Initialize primary node
my $node_primary = get_new_node('primary');
@ -85,7 +85,7 @@ sub test_target_session_attrs
my $node2_port = $node2->port;
my $node2_name = $node2->name;
my $target_name = $target_node->name;
my $target_name = $target_node->name if (defined $target_node);
# Build connection string for connection attempt.
my $connstr = "host=$node1_host,$node2_host ";
@ -97,10 +97,25 @@ sub test_target_session_attrs
my ($ret, $stdout, $stderr) =
$node1->psql('postgres', 'SHOW port;',
extra_params => [ '-d', $connstr ]);
is( $status == $ret && $stdout eq $target_node->port,
1,
"connect to node $target_name if mode \"$mode\" and $node1_name,$node2_name listed"
);
if ($status == 0)
{
is( $status == $ret && $stdout eq $target_node->port,
1,
"connect to node $target_name if mode \"$mode\" and $node1_name,$node2_name listed"
);
}
else
{
print "status = $status\n";
print "ret = $ret\n";
print "stdout = $stdout\n";
print "stderr = $stderr\n";
is( $status == $ret,
1,
"fail to connect to any nodes if mode \"$mode\" and $node1_name,$node2_name listed"
);
}
return;
}
@ -114,13 +129,64 @@ test_target_session_attrs($node_standby_1, $node_primary, $node_primary,
"read-write", 0);
# Connect to primary in "any" mode with primary,standby1 list.
test_target_session_attrs($node_primary, $node_standby_1, $node_primary, "any",
0);
test_target_session_attrs($node_primary, $node_standby_1, $node_primary,
"any", 0);
# Connect to standby1 in "any" mode with standby1,primary list.
test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1,
"any", 0);
# Connect to primary in "primary" mode with primary,standby1 list.
test_target_session_attrs($node_primary, $node_standby_1, $node_primary,
"primary", 0);
# Connect to primary in "primary" mode with standby1,primary list.
test_target_session_attrs($node_standby_1, $node_primary, $node_primary,
"primary", 0);
# Connect to standby1 in "read-only" mode with primary,standby1 list.
test_target_session_attrs($node_primary, $node_standby_1, $node_standby_1,
"read-only", 0);
# Connect to standby1 in "read-only" mode with standby1,primary list.
test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1,
"read-only", 0);
# Connect to primary in "prefer-standby" mode with primary,primary list.
test_target_session_attrs($node_primary, $node_primary, $node_primary,
"prefer-standby", 0);
# Connect to standby1 in "prefer-standby" mode with primary,standby1 list.
test_target_session_attrs($node_primary, $node_standby_1, $node_standby_1,
"prefer-standby", 0);
# Connect to standby1 in "prefer-standby" mode with standby1,primary list.
test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1,
"prefer-standby", 0);
# Connect to standby1 in "standby" mode with primary,standby1 list.
test_target_session_attrs($node_primary, $node_standby_1, $node_standby_1,
"standby", 0);
# Connect to standby1 in "standby" mode with standby1,primary list.
test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1,
"standby", 0);
# Fail to connect in "read-write" mode with standby1,standby2 list.
test_target_session_attrs($node_standby_1, $node_standby_2, undef,
"read-write", 2);
# Fail to connect in "primary" mode with standby1,standby2 list.
test_target_session_attrs($node_standby_1, $node_standby_2, undef,
"primary", 2);
# Fail to connect in "read-only" mode with primary,primary list.
test_target_session_attrs($node_primary, $node_primary, undef,
"read-only", 2);
# Fail to connect in "standby" mode with primary,primary list.
test_target_session_attrs($node_primary, $node_primary, undef, "standby", 2);
# Test for SHOW commands using a WAL sender connection with a replication
# role.
note "testing SHOW commands for replication connection";