diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 42b02b0a00c..cc20b0f234a 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1877,18 +1877,75 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
target_session_attrs
- If this parameter is set to read-write, only a
- connection in which read-write transactions are accepted by default
- is considered acceptable. The query
- SHOW transaction_read_only will be sent upon any
- successful connection; if it returns on, 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,
- any, regards all connections as acceptable.
-
+ 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:
+
+
+
+ any (default)
+
+
+ any successful connection is acceptable
+
+
+
+
+
+ read-write
+
+
+ session must accept read-write transactions by default (that
+ is, the server must not be in hot standby mode and
+ the default_transaction_read_only parameter
+ must be off)
+
+
+
+
+
+ read-only
+
+
+ session must not accept read-write transactions by default (the
+ converse)
+
+
+
+
+
+ primary
+
+
+ server must not be in hot standby mode
+
+
+
+
+
+ standby
+
+
+ server must be in hot standby mode
+
+
+
+
+
+ prefer-standby
+
+
+ first try to find a standby server, but if none of the listed
+ hosts is a standby server, try again in all
+ mode
+
+
+
+
+
-
+
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index db71fea169c..9812a14662d 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -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;
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index e7307533876..a5507538555 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -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;
+ }
}
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index effe0ccf856..47a098b4b93 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -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
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index ce36aabd25a..0c9e95f1a7a 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -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 */
diff --git a/src/test/recovery/t/001_stream_rep.pl b/src/test/recovery/t/001_stream_rep.pl
index 9e31a53de77..07a9912ce26 100644
--- a/src/test/recovery/t/001_stream_rep.pl
+++ b/src/test/recovery/t/001_stream_rep.pl
@@ -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";