Extend the abilities of libpq's target_session_attrs parameter.
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 3 Mar 2021 01:17:45 +0000 (20:17 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 3 Mar 2021 01:17:48 +0000 (20:17 -0500)
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

doc/src/sgml/libpq.sgml
src/interfaces/libpq/fe-connect.c
src/interfaces/libpq/fe-exec.c
src/interfaces/libpq/libpq-fe.h
src/interfaces/libpq/libpq-int.h
src/test/recovery/t/001_stream_rep.pl

index 42b02b0a00c0448c2f9eaacc7f38f0f4f0346e8b..cc20b0f234ab1ad01adbf7128bb142afd9a370b5 100644 (file)
@@ -1877,18 +1877,75 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       <term><literal>target_session_attrs</literal></term>
       <listitem>
        <para>
-        If this parameter is set to <literal>read-write</literal>, only a
-        connection in which read-write transactions are accepted by default
-        is considered acceptable.  The query
-        <literal>SHOW transaction_read_only</literal> will be sent upon any
-        successful connection; if it returns <literal>on</literal>, the connection
-        will be closed.  If multiple hosts were specified in the connection
-        string, any remaining servers will be tried just as if the connection
-        attempt had failed.  The default value of this parameter,
-        <literal>any</literal>, regards all connections as acceptable.
-      </para>
+        This option determines whether the session must have certain
+        properties to be acceptable.  It's typically used in combination
+        with multiple host names to select the first acceptable alternative
+        among several hosts.  There are six modes:
+
+        <variablelist>
+         <varlistentry>
+          <term><literal>any</literal> (default)</term>
+          <listitem>
+           <para>
+            any successful connection is acceptable
+           </para>
+          </listitem>
+         </varlistentry>
+
+         <varlistentry>
+          <term><literal>read-write</literal></term>
+          <listitem>
+           <para>
+            session must accept read-write transactions by default (that
+            is, the server must not be in hot standby mode and
+            the <varname>default_transaction_read_only</varname> parameter
+            must be <literal>off</literal>)
+           </para>
+          </listitem>
+         </varlistentry>
+
+         <varlistentry>
+          <term><literal>read-only</literal></term>
+          <listitem>
+           <para>
+            session must not accept read-write transactions by default (the
+            converse)
+           </para>
+          </listitem>
+         </varlistentry>
+
+         <varlistentry>
+          <term><literal>primary</literal></term>
+          <listitem>
+           <para>
+            server must not be in hot standby mode
+           </para>
+          </listitem>
+         </varlistentry>
+
+         <varlistentry>
+          <term><literal>standby</literal></term>
+          <listitem>
+           <para>
+            server must be in hot standby mode
+           </para>
+          </listitem>
+         </varlistentry>
+
+         <varlistentry>
+          <term><literal>prefer-standby</literal></term>
+          <listitem>
+           <para>
+            first try to find a standby server, but if none of the listed
+            hosts is a standby server, try again in <literal>all</literal>
+            mode
+           </para>
+          </listitem>
+         </varlistentry>
+        </variablelist>
+       </para>
       </listitem>
-    </varlistentry>
+     </varlistentry>
     </variablelist>
    </para>
   </sect2>
index db71fea169c96f1d417f843ee2ef9928efe4ceef..9812a14662dd3b669d9e82d346fb84880e3e1dcb 100644 (file)
@@ -356,7 +356,7 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
 
        {"target_session_attrs", "PGTARGETSESSIONATTRS",
                DefaultTargetSessionAttrs, NULL,
-               "Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */
+               "Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */
        offsetof(struct pg_conn, target_session_attrs)},
 
        /* Terminating entry --- MUST BE LAST */
@@ -583,6 +583,8 @@ pqDropServerData(PGconn *conn)
        conn->pstatus = NULL;
        conn->client_encoding = PG_SQL_ASCII;
        conn->std_strings = false;
+       conn->default_transaction_read_only = PG_BOOL_UNKNOWN;
+       conn->in_hot_standby = PG_BOOL_UNKNOWN;
        conn->sversion = 0;
 
        /* Drop large-object lookup data */
@@ -1389,33 +1391,46 @@ connectOptions2(PGconn *conn)
        }
 
        /*
-        * 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;
-       }
-
-       /*
-        * Validate target_session_attrs option.
+        * validate target_session_attrs option, and set target_server_type
         */
        if (conn->target_session_attrs)
        {
-               if (strcmp(conn->target_session_attrs, "any") != 0
-                       && strcmp(conn->target_session_attrs, "read-write") != 0)
+               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_settion_attrs",
+                                                         "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
+        */
+       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
@@ -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;
+
                                        /*
-                                        * 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
+                                        * "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.
                                         */
-                                       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 < 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)
+                               {
+                                       /*
+                                        * 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. */
@@ -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;
+                                       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)
                                        {
-                                               /* Not writable; fail this connection. */
-                                               PQclear(res);
+                                               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);
 
-                                               /* Append error report to conn->errorMessage. */
-                                               appendPQExpBufferStr(&conn->errorMessage,
-                                                                                        libpq_gettext("session is read-only\n"));
+                                       /* Finish reading messages before continuing */
+                                       conn->status = CONNECTION_CONSUME;
+                                       goto keep_going;
+                               }
 
-                                               /* Close connection politely. */
-                                               conn->status = CONNECTION_OK;
-                                               sendTerminateConn(conn);
+                               /* Something went wrong with "SHOW transaction_read_only". */
+                               if (res)
+                                       PQclear(res);
 
-                                               /*
-                                                * 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;
-                                       }
+                               /* Append error report to conn->errorMessage. */
+                               appendPQExpBufferStr(&conn->errorMessage,
+                                                                        libpq_gettext("\"SHOW transaction_read_only\" failed\n"));
+
+                               /* Close connection politely. */
+                               conn->status = CONNECTION_OK;
+                               sendTerminateConn(conn);
+
+                               /* Try next host. */
+                               conn->try_next_host = true;
+                               goto keep_going;
+                       }
 
-                                       /* Session is read-write, so we're good. */
+               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 any remaining messages before being
-                                        * considered as ready.
-                                        */
+                                       /* 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 "SELECT pg_is_in_recovery()". */
                                if (res)
                                        PQclear(res);
 
                                /* Append error report to 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. */
                                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;
                        }
 
@@ -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;
index e730753387615f3ff2bb531705afb8a5a02bf693..a5507538555c35332fefb7aaaf43f9797d628482 100644 (file)
@@ -1008,11 +1008,11 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
        }
 
        /*
-        * Special hacks: remember client_encoding and
-        * standard_conforming_strings, and convert server version to a numeric
-        * form.  We keep the first two of these in static variables as well, so
-        * that PQescapeString and PQescapeBytea can behave somewhat sanely (at
-        * least in single-connection-using programs).
+        * Save values of settings that are of interest to libpq in fields of the
+        * PGconn object.  We keep client_encoding and standard_conforming_strings
+        * in static variables as well, so that PQescapeString and PQescapeBytea
+        * can behave somewhat sanely (at least in single-connection-using
+        * programs).
         */
        if (strcmp(name, "client_encoding") == 0)
        {
@@ -1029,6 +1029,7 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
        }
        else if (strcmp(name, "server_version") == 0)
        {
+               /* We convert the server version to numeric form. */
                int                     cnt;
                int                     vmaj,
                                        vmin,
@@ -1062,6 +1063,16 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value)
                else
                        conn->sversion = 0; /* unknown */
        }
+       else if (strcmp(name, "default_transaction_read_only") == 0)
+       {
+               conn->default_transaction_read_only =
+                       (strcmp(value, "on") == 0) ? PG_BOOL_YES : PG_BOOL_NO;
+       }
+       else if (strcmp(name, "in_hot_standby") == 0)
+       {
+               conn->in_hot_standby =
+                       (strcmp(value, "on") == 0) ? PG_BOOL_YES : PG_BOOL_NO;
+       }
 }
 
 
index effe0ccf8566b1554c5c053fe927ff9d55d7c613..47a098b4b931b4b2e5a3366f319c35f028a3e43d 100644 (file)
@@ -63,12 +63,11 @@ typedef enum
        CONNECTION_SETENV,                      /* Negotiating environment. */
        CONNECTION_SSL_STARTUP,         /* Negotiating SSL. */
        CONNECTION_NEEDED,                      /* Internal state: connect() needed */
-       CONNECTION_CHECK_WRITABLE,      /* Check if we could make a writable
-                                                                * connection. */
-       CONNECTION_CONSUME,                     /* Wait for any pending message and consume
-                                                                * them. */
+       CONNECTION_CHECK_WRITABLE,      /* Checking if session is read-write. */
+       CONNECTION_CONSUME,                     /* Consuming any extra messages. */
        CONNECTION_GSS_STARTUP,         /* Negotiating GSSAPI. */
-       CONNECTION_CHECK_TARGET         /* Check if we have a proper target connection */
+       CONNECTION_CHECK_TARGET,        /* Checking target server properties. */
+       CONNECTION_CHECK_STANDBY        /* Checking if server is in standby mode. */
 } ConnStatusType;
 
 typedef enum
index ce36aabd25a9262e18314dc9a780a603626d0a66..0c9e95f1a7a0cda45611fb0e9cfd754162f75ef5 100644 (file)
@@ -232,6 +232,26 @@ typedef enum
        PGQUERY_DESCRIBE                        /* Describe Statement or Portal */
 } PGQueryClass;
 
+/* Target server type (decoded value of target_session_attrs) */
+typedef enum
+{
+       SERVER_TYPE_ANY = 0,            /* Any server (default) */
+       SERVER_TYPE_READ_WRITE,         /* Read-write server */
+       SERVER_TYPE_READ_ONLY,          /* Read-only server */
+       SERVER_TYPE_PRIMARY,            /* Primary server */
+       SERVER_TYPE_STANDBY,            /* Standby server */
+       SERVER_TYPE_PREFER_STANDBY, /* Prefer standby server */
+       SERVER_TYPE_PREFER_STANDBY_PASS2        /* second pass - behaves same as ANY */
+} PGTargetServerType;
+
+/* Boolean value plus a not-known state, for GUCs we might have to fetch */
+typedef enum
+{
+       PG_BOOL_UNKNOWN = 0,            /* Currently unknown */
+       PG_BOOL_YES,                            /* Yes (true) */
+       PG_BOOL_NO                                      /* No (false) */
+} PGTernaryBool;
+
 /* PGSetenvStatusType defines the state of the pqSetenv state machine */
 
 /* (this is used only for 2.0-protocol connections) */
@@ -370,9 +390,7 @@ struct pg_conn
                                                                 * "sspi") */
        char       *ssl_min_protocol_version;   /* minimum TLS protocol version */
        char       *ssl_max_protocol_version;   /* maximum TLS protocol version */
-
-       /* Type of connection to make.  Possible values: any, read-write. */
-       char       *target_session_attrs;
+       char       *target_session_attrs;       /* desired session properties */
 
        /* Optional file to write trace info to */
        FILE       *Pfdebug;
@@ -422,6 +440,7 @@ struct pg_conn
        char       *write_err_msg;      /* write error message, or NULL if OOM */
 
        /* Transient state needed while establishing connection */
+       PGTargetServerType target_server_type;  /* desired session properties */
        bool            try_next_addr;  /* time to advance to next address/host? */
        bool            try_next_host;  /* time to advance to next connhost[]? */
        struct addrinfo *addrlist;      /* list of addresses for current connhost */
@@ -437,6 +456,8 @@ struct pg_conn
        pgParameterStatus *pstatus; /* ParameterStatus data */
        int                     client_encoding;        /* encoding id */
        bool            std_strings;    /* standard_conforming_strings */
+       PGTernaryBool default_transaction_read_only;    /* default_transaction_read_only */
+       PGTernaryBool in_hot_standby;   /* in_hot_standby */
        PGVerbosity verbosity;          /* error/notice message verbosity */
        PGContextVisibility show_context;       /* whether to show CONTEXT field */
        PGlobjfuncs *lobjfuncs;         /* private state for large-object access fns */
index 9e31a53de7736e85f5f09002026dcc7bd336ed90..07a9912ce2667168c8d9df0fff5463b2d9970677 100644 (file)
@@ -3,7 +3,7 @@ use strict;
 use warnings;
 use PostgresNode;
 use TestLib;
-use Test::More tests => 36;
+use Test::More tests => 49;
 
 # Initialize primary node
 my $node_primary = get_new_node('primary');
@@ -85,7 +85,7 @@ sub test_target_session_attrs
        my $node2_port = $node2->port;
        my $node2_name = $node2->name;
 
-       my $target_name = $target_node->name;
+       my $target_name = $target_node->name if (defined $target_node);
 
        # Build connection string for connection attempt.
        my $connstr = "host=$node1_host,$node2_host ";
@@ -97,10 +97,25 @@ sub test_target_session_attrs
        my ($ret, $stdout, $stderr) =
          $node1->psql('postgres', 'SHOW port;',
                extra_params => [ '-d', $connstr ]);
-       is( $status == $ret && $stdout eq $target_node->port,
-               1,
-               "connect to node $target_name if mode \"$mode\" and $node1_name,$node2_name listed"
-       );
+       if ($status == 0)
+       {
+               is( $status == $ret && $stdout eq $target_node->port,
+                       1,
+                       "connect to node $target_name if mode \"$mode\" and $node1_name,$node2_name listed"
+               );
+       }
+       else
+       {
+               print "status = $status\n";
+               print "ret = $ret\n";
+               print "stdout = $stdout\n";
+               print "stderr = $stderr\n";
+
+               is( $status == $ret,
+                       1,
+                       "fail to connect to any nodes if mode \"$mode\" and $node1_name,$node2_name listed"
+               );
+       }
 
        return;
 }
@@ -114,13 +129,64 @@ test_target_session_attrs($node_standby_1, $node_primary, $node_primary,
        "read-write", 0);
 
 # Connect to primary in "any" mode with primary,standby1 list.
-test_target_session_attrs($node_primary, $node_standby_1, $node_primary, "any",
-       0);
+test_target_session_attrs($node_primary, $node_standby_1, $node_primary,
+       "any", 0);
 
 # Connect to standby1 in "any" mode with standby1,primary list.
 test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1,
        "any", 0);
 
+# Connect to primary in "primary" mode with primary,standby1 list.
+test_target_session_attrs($node_primary, $node_standby_1, $node_primary,
+       "primary", 0);
+
+# Connect to primary in "primary" mode with standby1,primary list.
+test_target_session_attrs($node_standby_1, $node_primary, $node_primary,
+       "primary", 0);
+
+# Connect to standby1 in "read-only" mode with primary,standby1 list.
+test_target_session_attrs($node_primary, $node_standby_1, $node_standby_1,
+       "read-only", 0);
+
+# Connect to standby1 in "read-only" mode with standby1,primary list.
+test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1,
+       "read-only", 0);
+
+# Connect to primary in "prefer-standby" mode with primary,primary list.
+test_target_session_attrs($node_primary, $node_primary, $node_primary,
+       "prefer-standby", 0);
+
+# Connect to standby1 in "prefer-standby" mode with primary,standby1 list.
+test_target_session_attrs($node_primary, $node_standby_1, $node_standby_1,
+       "prefer-standby", 0);
+
+# Connect to standby1 in "prefer-standby" mode with standby1,primary list.
+test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1,
+       "prefer-standby", 0);
+
+# Connect to standby1 in "standby" mode with primary,standby1 list.
+test_target_session_attrs($node_primary, $node_standby_1, $node_standby_1,
+       "standby", 0);
+
+# Connect to standby1 in "standby" mode with standby1,primary list.
+test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1,
+       "standby", 0);
+
+# Fail to connect in "read-write" mode with standby1,standby2 list.
+test_target_session_attrs($node_standby_1, $node_standby_2, undef,
+       "read-write", 2);
+
+# Fail to connect in "primary" mode with standby1,standby2 list.
+test_target_session_attrs($node_standby_1, $node_standby_2, undef,
+       "primary", 2);
+
+# Fail to connect in "read-only" mode with primary,primary list.
+test_target_session_attrs($node_primary, $node_primary, undef,
+       "read-only", 2);
+
+# Fail to connect in "standby" mode with primary,primary list.
+test_target_session_attrs($node_primary, $node_primary, undef, "standby", 2);
+
 # Test for SHOW commands using a WAL sender connection with a replication
 # role.
 note "testing SHOW commands for replication connection";