mirror of
https://github.com/postgres/postgres.git
synced 2025-06-25 01:02:05 +03:00
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:
@ -1877,18 +1877,75 @@ 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>
|
<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>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
</variablelist>
|
</variablelist>
|
||||||
</para>
|
</para>
|
||||||
</sect2>
|
</sect2>
|
||||||
|
@ -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)
|
|||||||
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 */
|
||||||
@ -1388,6 +1390,36 @@ connectOptions2(PGconn *conn)
|
|||||||
goto oom_error;
|
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
|
* Resolve special "auto" client_encoding from the locale
|
||||||
*/
|
*/
|
||||||
@ -1400,23 +1432,6 @@ connectOptions2(PGconn *conn)
|
|||||||
goto oom_error;
|
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
|
* 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
|
* 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->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)
|
|||||||
/* 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)
|
|||||||
/* 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
|
|||||||
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.
|
||||||
*/
|
*/
|
||||||
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 */
|
/* Drop any address info for previous host */
|
||||||
release_conn_addrinfo(conn);
|
release_conn_addrinfo(conn);
|
||||||
@ -3550,28 +3584,131 @@ 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
|
|
||||||
* may just skip the test in that case.
|
|
||||||
*/
|
*/
|
||||||
if (conn->sversion >= 70400 &&
|
if (conn->target_server_type == SERVER_TYPE_READ_WRITE ||
|
||||||
conn->target_session_attrs != NULL &&
|
conn->target_server_type == SERVER_TYPE_READ_ONLY)
|
||||||
strcmp(conn->target_session_attrs, "read-write") == 0)
|
{
|
||||||
|
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
|
* If the server didn't report "in_hot_standby" at
|
||||||
* does not get cleared. We need to preserve any error
|
* startup, we must determine its state by sending the
|
||||||
* messages related to previous hosts we have tried and
|
* query "SELECT pg_catalog.pg_is_in_recovery()". Servers
|
||||||
* failed to connect to.
|
* 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 (conn->sversion < 90000)
|
||||||
if (!PQsendQueryContinue(conn,
|
conn->in_hot_standby = PG_BOOL_NO;
|
||||||
"SHOW transaction_read_only"))
|
|
||||||
goto error_return;
|
if (conn->in_hot_standby == PG_BOOL_UNKNOWN)
|
||||||
conn->status = CONNECTION_CHECK_WRITABLE;
|
{
|
||||||
return PGRES_POLLING_READING;
|
/*
|
||||||
|
* 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. */
|
||||||
@ -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
|
|||||||
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
|
|||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
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);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Finish reading any remaining messages before being
|
* "transaction_read_only = on" proves that at least one
|
||||||
* considered as ready.
|
* 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;
|
conn->status = CONNECTION_CONSUME;
|
||||||
goto keep_going;
|
goto keep_going;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/* Something went wrong with "SHOW transaction_read_only". */
|
||||||
* 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("\"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 address */
|
/* Try next host. */
|
||||||
conn->try_next_addr = true;
|
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;
|
goto keep_going;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 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)
|
|||||||
}
|
}
|
||||||
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)
|
|||||||
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
|
|||||||
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
|
|||||||
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
|
|||||||
* "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
|
|||||||
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
|
|||||||
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;
|
|||||||
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
|
|||||||
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
|
|||||||
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 ]);
|
||||||
is( $status == $ret && $stdout eq $target_node->port,
|
if ($status == 0)
|
||||||
1,
|
{
|
||||||
"connect to node $target_name if mode \"$mode\" and $node1_name,$node2_name listed"
|
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;
|
return;
|
||||||
}
|
}
|
||||||
@ -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";
|
||||||
|
Reference in New Issue
Block a user