Commit ee28cacf authored by Tom Lane's avatar Tom Lane

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
parent 57e6db70
...@@ -1877,15 +1877,72 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname ...@@ -1877,15 +1877,72 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>target_session_attrs</literal></term> <term><literal>target_session_attrs</literal></term>
<listitem> <listitem>
<para> <para>
If this parameter is set to <literal>read-write</literal>, only a This option determines whether the session must have certain
connection in which read-write transactions are accepted by default properties to be acceptable. It's typically used in combination
is considered acceptable. The query with multiple host names to select the first acceptable alternative
<literal>SHOW transaction_read_only</literal> will be sent upon any among several hosts. There are six modes:
successful connection; if it returns <literal>on</literal>, the connection
will be closed. If multiple hosts were specified in the connection <variablelist>
string, any remaining servers will be tried just as if the connection <varlistentry>
attempt had failed. The default value of this parameter, <term><literal>any</literal> (default)</term>
<literal>any</literal>, regards all connections as acceptable. <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> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
......
...@@ -356,7 +356,7 @@ static const internalPQconninfoOption PQconninfoOptions[] = { ...@@ -356,7 +356,7 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
{"target_session_attrs", "PGTARGETSESSIONATTRS", {"target_session_attrs", "PGTARGETSESSIONATTRS",
DefaultTargetSessionAttrs, NULL, 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)}, offsetof(struct pg_conn, target_session_attrs)},
/* Terminating entry --- MUST BE LAST */ /* Terminating entry --- MUST BE LAST */
...@@ -583,6 +583,8 @@ pqDropServerData(PGconn *conn) ...@@ -583,6 +583,8 @@ pqDropServerData(PGconn *conn)
conn->pstatus = NULL; conn->pstatus = NULL;
conn->client_encoding = PG_SQL_ASCII; conn->client_encoding = PG_SQL_ASCII;
conn->std_strings = false; conn->std_strings = false;
conn->default_transaction_read_only = PG_BOOL_UNKNOWN;
conn->in_hot_standby = PG_BOOL_UNKNOWN;
conn->sversion = 0; conn->sversion = 0;
/* Drop large-object lookup data */ /* Drop large-object lookup data */
...@@ -1389,33 +1391,46 @@ connectOptions2(PGconn *conn) ...@@ -1389,33 +1391,46 @@ connectOptions2(PGconn *conn)
} }
/* /*
* Resolve special "auto" client_encoding from the locale * validate target_session_attrs option, and set target_server_type
*/
if (conn->client_encoding_initial &&
strcmp(conn->client_encoding_initial, "auto") == 0)
{
free(conn->client_encoding_initial);
conn->client_encoding_initial = strdup(pg_encoding_to_char(pg_get_encoding_from_locale(NULL, true)));
if (!conn->client_encoding_initial)
goto oom_error;
}
/*
* Validate target_session_attrs option.
*/ */
if (conn->target_session_attrs) if (conn->target_session_attrs)
{ {
if (strcmp(conn->target_session_attrs, "any") != 0 if (strcmp(conn->target_session_attrs, "any") == 0)
&& strcmp(conn->target_session_attrs, "read-write") != 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; conn->status = CONNECTION_BAD;
appendPQExpBuffer(&conn->errorMessage, appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("invalid %s value: \"%s\"\n"), libpq_gettext("invalid %s value: \"%s\"\n"),
"target_settion_attrs", "target_session_attrs",
conn->target_session_attrs); conn->target_session_attrs);
return false; return false;
} }
} }
else
conn->target_server_type = SERVER_TYPE_ANY;
/*
* Resolve special "auto" client_encoding from the locale
*/
if (conn->client_encoding_initial &&
strcmp(conn->client_encoding_initial, "auto") == 0)
{
free(conn->client_encoding_initial);
conn->client_encoding_initial = strdup(pg_encoding_to_char(pg_get_encoding_from_locale(NULL, true)));
if (!conn->client_encoding_initial)
goto oom_error;
}
/* /*
* Only if we get this far is it appropriate to try to connect. (We need a * Only if we get this far is it appropriate to try to connect. (We need a
...@@ -2057,6 +2072,10 @@ connectDBStart(PGconn *conn) ...@@ -2057,6 +2072,10 @@ connectDBStart(PGconn *conn)
conn->try_next_host = true; conn->try_next_host = true;
conn->status = CONNECTION_NEEDED; 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(), * The code for processing CONNECTION_NEEDED state is in PQconnectPoll(),
* so that it can easily be re-executed if needed again during the * so that it can easily be re-executed if needed again during the
...@@ -2250,6 +2269,9 @@ PQconnectPoll(PGconn *conn) ...@@ -2250,6 +2269,9 @@ PQconnectPoll(PGconn *conn)
/* These are reading states */ /* These are reading states */
case CONNECTION_AWAITING_RESPONSE: case CONNECTION_AWAITING_RESPONSE:
case CONNECTION_AUTH_OK: case CONNECTION_AUTH_OK:
case CONNECTION_CHECK_WRITABLE:
case CONNECTION_CONSUME:
case CONNECTION_CHECK_STANDBY:
{ {
/* Load waiting data */ /* Load waiting data */
int n = pqReadData(conn); int n = pqReadData(conn);
...@@ -2274,9 +2296,8 @@ PQconnectPoll(PGconn *conn) ...@@ -2274,9 +2296,8 @@ PQconnectPoll(PGconn *conn)
/* Special cases: proceed without waiting. */ /* Special cases: proceed without waiting. */
case CONNECTION_SSL_STARTUP: case CONNECTION_SSL_STARTUP:
case CONNECTION_NEEDED: case CONNECTION_NEEDED:
case CONNECTION_CHECK_WRITABLE:
case CONNECTION_CONSUME:
case CONNECTION_GSS_STARTUP: case CONNECTION_GSS_STARTUP:
case CONNECTION_CHECK_TARGET:
break; break;
default: default:
...@@ -2311,15 +2332,28 @@ keep_going: /* We will come back to here until there is ...@@ -2311,15 +2332,28 @@ keep_going: /* We will come back to here until there is
int ret; int ret;
char portstr[MAXPGPATH]; 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 * Oops, no more hosts.
* set up, so just set the right status. *
* 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.
*/ */
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; goto error_return;
} }
conn->whichhost++;
/* Drop any address info for previous host */ /* Drop any address info for previous host */
release_conn_addrinfo(conn); release_conn_addrinfo(conn);
...@@ -3550,30 +3584,133 @@ keep_going: /* We will come back to here until there is ...@@ -3550,30 +3584,133 @@ keep_going: /* We will come back to here until there is
case CONNECTION_CHECK_TARGET: case CONNECTION_CHECK_TARGET:
{ {
/* /*
* If a read-write connection is required, see if we have one. * If a read-write, read-only, primary, or standby 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 if (conn->target_server_type == SERVER_TYPE_READ_WRITE ||
* may just skip the test in that case. 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 && if (conn->sversion < 70400)
conn->target_session_attrs != NULL && {
strcmp(conn->target_session_attrs, "read-write") == 0) 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 * We use PQsendQueryContinue so that
* does not get cleared. We need to preserve any error * conn->errorMessage does not get cleared. We need
* messages related to previous hosts we have tried and * to preserve any error messages related to previous
* failed to connect to. * hosts we have tried and failed to connect to.
*/ */
conn->status = CONNECTION_OK; conn->status = CONNECTION_OK;
if (!PQsendQueryContinue(conn, if (!PQsendQueryContinue(conn,
"SHOW transaction_read_only")) "SHOW transaction_read_only"))
goto error_return; goto error_return;
/* We'll return to this state when we have the answer */
conn->status = CONNECTION_CHECK_WRITABLE; conn->status = CONNECTION_CHECK_WRITABLE;
return PGRES_POLLING_READING; 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)
{
/*
* 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.
*/
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. */ /* We can release the address list now. */
release_conn_addrinfo(conn); release_conn_addrinfo(conn);
...@@ -3617,6 +3754,14 @@ keep_going: /* We will come back to here until there is ...@@ -3617,6 +3754,14 @@ keep_going: /* We will come back to here until there is
case CONNECTION_CONSUME: 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; conn->status = CONNECTION_OK;
if (!PQconsumeInput(conn)) if (!PQconsumeInput(conn))
goto error_return; goto error_return;
...@@ -3627,26 +3772,26 @@ keep_going: /* We will come back to here until there is ...@@ -3627,26 +3772,26 @@ keep_going: /* We will come back to here until there is
return PGRES_POLLING_READING; return PGRES_POLLING_READING;
} }
/* /* Call PQgetResult() again until we get a NULL result */
* Call PQgetResult() again to consume NULL result.
*/
res = PQgetResult(conn); res = PQgetResult(conn);
if (res != NULL) if (res != NULL)
{ {
PQclear(res); PQclear(res);
conn->status = CONNECTION_CONSUME; conn->status = CONNECTION_CONSUME;
goto keep_going; return PGRES_POLLING_READING;
} }
/* We can release the address list now. */ conn->status = CONNECTION_CHECK_TARGET;
release_conn_addrinfo(conn); goto keep_going;
/* We are open for business! */
conn->status = CONNECTION_OK;
return PGRES_POLLING_OK;
} }
case CONNECTION_CHECK_WRITABLE: 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; conn->status = CONNECTION_OK;
if (!PQconsumeInput(conn)) if (!PQconsumeInput(conn))
goto error_return; goto error_return;
...@@ -3658,61 +3803,102 @@ keep_going: /* We will come back to here until there is ...@@ -3658,61 +3803,102 @@ keep_going: /* We will come back to here until there is
} }
res = PQgetResult(conn); res = PQgetResult(conn);
if (res && (PQresultStatus(res) == PGRES_TUPLES_OK) && if (res && PQresultStatus(res) == PGRES_TUPLES_OK &&
PQntuples(res) == 1) PQntuples(res) == 1)
{ {
char *val; char *val = PQgetvalue(res, 0, 0);
val = PQgetvalue(res, 0, 0); /*
* "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) if (strncmp(val, "on", 2) == 0)
{ {
/* Not writable; fail this connection. */ 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". */
if (res)
PQclear(res); PQclear(res);
/* Append error report to conn->errorMessage. */ /* Append error report to conn->errorMessage. */
appendPQExpBufferStr(&conn->errorMessage, appendPQExpBufferStr(&conn->errorMessage,
libpq_gettext("session is read-only\n")); libpq_gettext("\"SHOW transaction_read_only\" failed\n"));
/* Close connection politely. */ /* Close connection politely. */
conn->status = CONNECTION_OK; conn->status = CONNECTION_OK;
sendTerminateConn(conn); sendTerminateConn(conn);
/* /* Try next host. */
* Try next host if any, but we don't want to consider
* additional addresses for this host.
*/
conn->try_next_host = true; conn->try_next_host = true;
goto keep_going; goto keep_going;
} }
/* Session is read-write, so we're good. */ case CONNECTION_CHECK_STANDBY:
PQclear(res); {
/* /*
* Finish reading any remaining messages before being * Waiting for result of "SELECT pg_is_in_recovery()". We
* considered as ready. * 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; conn->status = CONNECTION_CONSUME;
goto keep_going; goto keep_going;
} }
/* /* Something went wrong with "SELECT pg_is_in_recovery()". */
* Something went wrong with "SHOW transaction_read_only". We
* should try next addresses.
*/
if (res) if (res)
PQclear(res); PQclear(res);
/* Append error report to conn->errorMessage. */ /* Append error report to conn->errorMessage. */
appendPQExpBufferStr(&conn->errorMessage, appendPQExpBufferStr(&conn->errorMessage,
libpq_gettext("test \"SHOW transaction_read_only\" failed\n")); libpq_gettext("\"SELECT pg_is_in_recovery()\" failed\n"));
/* Close connection politely. */ /* Close connection politely. */
conn->status = CONNECTION_OK; conn->status = CONNECTION_OK;
sendTerminateConn(conn); sendTerminateConn(conn);
/* Try next address */ /* Try next host. */
conn->try_next_addr = true; conn->try_next_host = true;
goto keep_going; goto keep_going;
} }
...@@ -3859,6 +4045,8 @@ makeEmptyPGconn(void) ...@@ -3859,6 +4045,8 @@ makeEmptyPGconn(void)
conn->setenv_state = SETENV_STATE_IDLE; conn->setenv_state = SETENV_STATE_IDLE;
conn->client_encoding = PG_SQL_ASCII; conn->client_encoding = PG_SQL_ASCII;
conn->std_strings = false; /* unless server says differently */ 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->verbosity = PQERRORS_DEFAULT;
conn->show_context = PQSHOW_CONTEXT_ERRORS; conn->show_context = PQSHOW_CONTEXT_ERRORS;
conn->sock = PGINVALID_SOCKET; conn->sock = PGINVALID_SOCKET;
......
...@@ -1008,11 +1008,11 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value) ...@@ -1008,11 +1008,11 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
} }
/* /*
* Special hacks: remember client_encoding and * Save values of settings that are of interest to libpq in fields of the
* standard_conforming_strings, and convert server version to a numeric * PGconn object. We keep client_encoding and standard_conforming_strings
* form. We keep the first two of these in static variables as well, so * in static variables as well, so that PQescapeString and PQescapeBytea
* that PQescapeString and PQescapeBytea can behave somewhat sanely (at * can behave somewhat sanely (at least in single-connection-using
* least in single-connection-using programs). * programs).
*/ */
if (strcmp(name, "client_encoding") == 0) if (strcmp(name, "client_encoding") == 0)
{ {
...@@ -1029,6 +1029,7 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value) ...@@ -1029,6 +1029,7 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
} }
else if (strcmp(name, "server_version") == 0) else if (strcmp(name, "server_version") == 0)
{ {
/* We convert the server version to numeric form. */
int cnt; int cnt;
int vmaj, int vmaj,
vmin, vmin,
...@@ -1062,6 +1063,16 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value) ...@@ -1062,6 +1063,16 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
else else
conn->sversion = 0; /* unknown */ 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;
}
} }
......
...@@ -63,12 +63,11 @@ typedef enum ...@@ -63,12 +63,11 @@ typedef enum
CONNECTION_SETENV, /* Negotiating environment. */ CONNECTION_SETENV, /* Negotiating environment. */
CONNECTION_SSL_STARTUP, /* Negotiating SSL. */ CONNECTION_SSL_STARTUP, /* Negotiating SSL. */
CONNECTION_NEEDED, /* Internal state: connect() needed */ CONNECTION_NEEDED, /* Internal state: connect() needed */
CONNECTION_CHECK_WRITABLE, /* Check if we could make a writable CONNECTION_CHECK_WRITABLE, /* Checking if session is read-write. */
* connection. */ CONNECTION_CONSUME, /* Consuming any extra messages. */
CONNECTION_CONSUME, /* Wait for any pending message and consume
* them. */
CONNECTION_GSS_STARTUP, /* Negotiating GSSAPI. */ 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; } ConnStatusType;
typedef enum typedef enum
......
...@@ -232,6 +232,26 @@ typedef enum ...@@ -232,6 +232,26 @@ typedef enum
PGQUERY_DESCRIBE /* Describe Statement or Portal */ PGQUERY_DESCRIBE /* Describe Statement or Portal */
} PGQueryClass; } 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 */ /* PGSetenvStatusType defines the state of the pqSetenv state machine */
/* (this is used only for 2.0-protocol connections) */ /* (this is used only for 2.0-protocol connections) */
...@@ -370,9 +390,7 @@ struct pg_conn ...@@ -370,9 +390,7 @@ struct pg_conn
* "sspi") */ * "sspi") */
char *ssl_min_protocol_version; /* minimum TLS protocol version */ char *ssl_min_protocol_version; /* minimum TLS protocol version */
char *ssl_max_protocol_version; /* maximum TLS protocol version */ char *ssl_max_protocol_version; /* maximum TLS protocol version */
char *target_session_attrs; /* desired session properties */
/* Type of connection to make. Possible values: any, read-write. */
char *target_session_attrs;
/* Optional file to write trace info to */ /* Optional file to write trace info to */
FILE *Pfdebug; FILE *Pfdebug;
...@@ -422,6 +440,7 @@ struct pg_conn ...@@ -422,6 +440,7 @@ struct pg_conn
char *write_err_msg; /* write error message, or NULL if OOM */ char *write_err_msg; /* write error message, or NULL if OOM */
/* Transient state needed while establishing connection */ /* 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_addr; /* time to advance to next address/host? */
bool try_next_host; /* time to advance to next connhost[]? */ bool try_next_host; /* time to advance to next connhost[]? */
struct addrinfo *addrlist; /* list of addresses for current connhost */ struct addrinfo *addrlist; /* list of addresses for current connhost */
...@@ -437,6 +456,8 @@ struct pg_conn ...@@ -437,6 +456,8 @@ struct pg_conn
pgParameterStatus *pstatus; /* ParameterStatus data */ pgParameterStatus *pstatus; /* ParameterStatus data */
int client_encoding; /* encoding id */ int client_encoding; /* encoding id */
bool std_strings; /* standard_conforming_strings */ 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 */ PGVerbosity verbosity; /* error/notice message verbosity */
PGContextVisibility show_context; /* whether to show CONTEXT field */ PGContextVisibility show_context; /* whether to show CONTEXT field */
PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */
......
...@@ -3,7 +3,7 @@ use strict; ...@@ -3,7 +3,7 @@ use strict;
use warnings; use warnings;
use PostgresNode; use PostgresNode;
use TestLib; use TestLib;
use Test::More tests => 36; use Test::More tests => 49;
# Initialize primary node # Initialize primary node
my $node_primary = get_new_node('primary'); my $node_primary = get_new_node('primary');
...@@ -85,7 +85,7 @@ sub test_target_session_attrs ...@@ -85,7 +85,7 @@ sub test_target_session_attrs
my $node2_port = $node2->port; my $node2_port = $node2->port;
my $node2_name = $node2->name; 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. # Build connection string for connection attempt.
my $connstr = "host=$node1_host,$node2_host "; my $connstr = "host=$node1_host,$node2_host ";
...@@ -97,10 +97,25 @@ sub test_target_session_attrs ...@@ -97,10 +97,25 @@ sub test_target_session_attrs
my ($ret, $stdout, $stderr) = my ($ret, $stdout, $stderr) =
$node1->psql('postgres', 'SHOW port;', $node1->psql('postgres', 'SHOW port;',
extra_params => [ '-d', $connstr ]); extra_params => [ '-d', $connstr ]);
if ($status == 0)
{
is( $status == $ret && $stdout eq $target_node->port, is( $status == $ret && $stdout eq $target_node->port,
1, 1,
"connect to node $target_name if mode \"$mode\" and $node1_name,$node2_name listed" "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; return;
} }
...@@ -114,13 +129,64 @@ test_target_session_attrs($node_standby_1, $node_primary, $node_primary, ...@@ -114,13 +129,64 @@ test_target_session_attrs($node_standby_1, $node_primary, $node_primary,
"read-write", 0); "read-write", 0);
# Connect to primary in "any" mode with primary,standby1 list. # Connect to primary in "any" mode with primary,standby1 list.
test_target_session_attrs($node_primary, $node_standby_1, $node_primary, "any", test_target_session_attrs($node_primary, $node_standby_1, $node_primary,
0); "any", 0);
# Connect to standby1 in "any" mode with standby1,primary list. # Connect to standby1 in "any" mode with standby1,primary list.
test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1, test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1,
"any", 0); "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 # Test for SHOW commands using a WAL sender connection with a replication
# role. # role.
note "testing SHOW commands for replication connection"; note "testing SHOW commands for replication connection";
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment