On Tue, Feb 9, 2021 at 5:47 AM Greg Nancarrow <gregn4...@gmail.com> wrote: > > On Mon, Feb 8, 2021 at 8:17 PM vignesh C <vignes...@gmail.com> wrote: > > > > > > > > I think what we want to do is mark default_transaction_read_only as > > > GUC_REPORT, instead. That will give a reliable report of what the > > > state of its GUC is, and you can combine it with is_hot_standby > > > to decide whether the session should be considered read-only. > > > If you don't get those two GUC values during connection, then you > > > can fall back on "SHOW transaction_read_only". > > > > > > > I have made a patch for the above with the changes suggested and > > rebased it with the head code. > > Attached v21 patch which has the changes for the same. > > Thoughts? > > > > I'm still looking at the patch code, but I noticed that the > documentation update describing how support of read-write transactions > is determined isn't quite right and it isn't clear how the parameters > work. > I'd suggest something like the following (you'd need to fix the line > lengths and line-wrapping appropriately) - please check it for > correctness: > > <para> > The support of read-write transactions is determined by the value of > the > <varname>default_transaction_read_only</varname> and > <varname>in_hot_standby</varname> configuration parameters, > that, if supported, > are reported by the server upon successful connection. If the > value of either > of these parameters is <literal>on</literal>, it means the > server doesn't support > read-write transactions. If either/both of these parameters > are not reported, > then the support of read-write transactions is determined by > an explicit query, > by sending <literal>SHOW transaction_read_only</literal> after > successful > connection; if it returns <literal>on</literal>, it means the > server doesn't > support read-write transactions. The standby mode state is > determined by either > the value of the <varname>in_hot_standby</varname> configuration > parameter, that is reported by the server (if supported) upon > successful connection, or is otherwise explicitly queried by sending > <literal>SELECT pg_is_in_recovery()</literal> after successful > connection; if it returns <literal>t</literal>, it means the server is > in hot standby mode. > </para>
Thanks Greg for the comments, Please find the attached v22 patch having the fix for the same. Thoughts? Regards, Vignesh
From db9b894d8a8c51de80e6b3b7b8c660dda5374ff9 Mon Sep 17 00:00:00 2001 From: Vignesh C <vignes...@gmail.com> Date: Mon, 8 Feb 2021 11:23:31 +0530 Subject: [PATCH v22] Enhance the libpq "target_session_attrs" connection parameter. Enhance the connection parameter "target_session_attrs" to support new values: read-only/primary/standby/prefer-standby. Add a new "read-only" target_session_attrs option value, to support connecting to a read-only server if available from the list of hosts (otherwise the connection attempt fails). Add a new "primary" target_session_attrs option value, to support connecting to a server which is not in hot-standby mode, if available from the list of hosts (otherwise the connection attempt fails). Add a new "standby" target_session_attrs option value, to support connecting to a server which is in hot-standby mode, if available from the list of hosts (otherwise the connection attempt fails). Add a new "prefer-standby" target_session_attrs option value, to support connecting to a server which is in hot-standby mode, if available from the list of hosts (otherwise connect to a server which is not in hot-standby mode). Discussion: https://www.postgresql.org/message-id/flat/caf3+xm+8-ztokav9ghij3wfgentq97qcjxqt+rbfq6f7onz...@mail.gmail.com --- doc/src/sgml/high-availability.sgml | 16 +- doc/src/sgml/libpq.sgml | 73 +++++- doc/src/sgml/protocol.sgml | 5 +- src/backend/utils/misc/guc.c | 3 +- src/interfaces/libpq/fe-connect.c | 432 ++++++++++++++++++++++++++++++---- src/interfaces/libpq/fe-exec.c | 18 +- src/interfaces/libpq/libpq-fe.h | 3 +- src/interfaces/libpq/libpq-int.h | 52 +++- src/test/recovery/t/001_stream_rep.pl | 79 ++++++- src/tools/pgindent/typedefs.list | 2 + 10 files changed, 602 insertions(+), 81 deletions(-) diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml index f49f5c0..2bbd52c 100644 --- a/doc/src/sgml/high-availability.sgml +++ b/doc/src/sgml/high-availability.sgml @@ -1700,14 +1700,14 @@ synchronous_standby_names = 'ANY 2 (s1, s2, s3)' </para> <para> - During hot standby, the parameter <varname>transaction_read_only</varname> is always - true and may not be changed. But as long as no attempt is made to modify - the database, connections during hot standby will act much like any other - database connection. If failover or switchover occurs, the database will - switch to normal processing mode. Sessions will remain connected while the - server changes mode. Once hot standby finishes, it will be possible to - initiate read-write transactions (even from a session begun during - hot standby). + During hot standby, the parameter <varname>in_hot_standby</varname> and + <varname>transaction_read_only</varname> are always true and may not be + changed. But as long as no attempt is made to modify the database, + connections during hot standby will act much like any other database + connection. If failover or switchover occurs, the database will switch to + normal processing mode. Sessions will remain connected while the server + changes mode. Once hot standby finishes, it will be possible to initiate + read-write transactions (even from a session begun during hot standby). </para> <para> diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index b7a8245..d5f0f24 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1836,18 +1836,66 @@ 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> + The supported options for this parameter are <literal>any</literal>, + <literal>read-write</literal>, <literal>read-only</literal>, + <literal>primary</literal>, <literal>standby</literal> and + <literal>prefer-standby</literal>. + The default value of this parameter, <literal>any</literal>, regards + all connections as acceptable. If multiple hosts are specified in the + connection string, each host is tried in the order given until a connection + is successful. + </para> + + <para> + The support of read-write transactions is determined by the value of the + <varname>default_transaction_read_only</varname> and + <varname>in_hot_standby</varname> configuration parameters, that is + reported by the server (if supported) upon successful connection. If + the value of either of these parameters is <literal>on</literal>, it + means the server doesn't support read-write transactions. If + either/both of these parameters are not reported, then the support of + read-write transactions is determined by an explicit query, by sending + <literal>SHOW transaction_read_only</literal> after successful + connection; if it returns <literal>on</literal>, it means the server + doesn't support read-write transactions. The standby mode state is + determined by either the value of the <varname>in_hot_standby</varname> + configuration parameter, that is reported by the server (if supported) + upon successful connection, or is otherwise explicitly queried by + sending <literal>SELECT pg_is_in_recovery()</literal> after successful + connection; if it returns <literal>t</literal>, it means the server is + in hot standby mode. + </para> + + <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. + </para> + + <para> + If this parameter is set to <literal>read-only</literal>, only a connection in + which read-only transactions are accepted by default is considered acceptable. + </para> + + <para> + If this parameter is set to <literal>primary</literal>, then only a connection in + which the server is not in hot standby mode is considered acceptable. + </para> + + <para> + If this parameter is set to <literal>standby</literal>, then only a connection in + which the server is in hot standby mode is considered acceptable. + </para> + + <para> + If this parameter is set to <literal>prefer-standby</literal>, then a connection + in which the server is in hot standby mode is preferred. Otherwise, if no such + connections can be found, then a connection in which the server is not in hot + standby mode will be considered. + </para> + </listitem> - </varlistentry> + </varlistentry> + </variablelist> </para> </sect2> @@ -2150,6 +2198,7 @@ const char *PQparameterStatus(const PGconn *conn, const char *paramName); <varname>server_encoding</varname>, <varname>client_encoding</varname>, <varname>application_name</varname>, + <varname>default_transaction_read_only</varname>, <varname>in_hot_standby</varname>, <varname>is_superuser</varname>, <varname>session_authorization</varname>, @@ -2165,6 +2214,7 @@ const char *PQparameterStatus(const PGconn *conn, const char *paramName); <varname>IntervalStyle</varname> was not reported by releases before 8.4; <varname>application_name</varname> was not reported by releases before 9.0; + <varname>default_transaction_read_only</varname> and <varname>in_hot_standby</varname> was not reported by releases before 14.) Note that @@ -7268,6 +7318,7 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) linkend="libpq-connect-target-session-attrs"/> connection parameter. </para> </listitem> + </itemizedlist> </para> diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 3763b4b..57d1f2b 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -1278,6 +1278,7 @@ SELCT 1/0;<!-- this typo is intentional --> <varname>server_encoding</varname>, <varname>client_encoding</varname>, <varname>application_name</varname>, + <varname>default_transaction_read_only</varname>, <varname>in_hot_standby</varname>, <varname>is_superuser</varname>, <varname>session_authorization</varname>, @@ -1293,8 +1294,8 @@ SELCT 1/0;<!-- this typo is intentional --> <varname>IntervalStyle</varname> was not reported by releases before 8.4; <varname>application_name</varname> was not reported by releases before 9.0; - <varname>in_hot_standby</varname> was not reported by releases before - 14.) + <varname>default_transaction_read_only</varname> and + <varname>in_hot_standby</varname> were not reported by releases before 14.0) Note that <varname>server_version</varname>, <varname>server_encoding</varname> and diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index eafdb11..3dba213 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -1619,7 +1619,8 @@ static struct config_bool ConfigureNamesBool[] = { {"default_transaction_read_only", PGC_USERSET, CLIENT_CONN_STATEMENT, gettext_noop("Sets the default read-only status of new transactions."), - NULL + NULL, + GUC_REPORT }, &DefaultXactReadOnly, false, diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 8ca0583..e389a6d 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -352,7 +352,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 */ @@ -1006,6 +1006,33 @@ parse_comma_separated_list(char **startptr, bool *more) } /* + * validateAndGetTargetServerType + * + * Validate a given target_session_attrs value and get the requested server type. + * + * Returns true if OK, false if the specified option value is invalid. + */ +static bool +validateAndGetTargetServerType(const char *optionValue, TargetServerType *requestedServerType) +{ + if (strcmp(optionValue, "any") == 0) + *requestedServerType = SERVER_TYPE_ANY; + else if (strcmp(optionValue, "primary") == 0) + *requestedServerType = SERVER_TYPE_PRIMARY; + else if (strcmp(optionValue, "read-write") == 0) + *requestedServerType = SERVER_TYPE_READ_WRITE; + else if (strcmp(optionValue, "read-only") == 0) + *requestedServerType = SERVER_TYPE_READ_ONLY; + else if (strcmp(optionValue, "prefer-standby") == 0) + *requestedServerType = SERVER_TYPE_PREFER_STANDBY; + else if (strcmp(optionValue, "standby") == 0) + *requestedServerType = SERVER_TYPE_STANDBY; + else + return false; + return true; +} + +/* * connectOptions2 * * Compute derived connection options after absorbing all user-supplied info. @@ -1401,13 +1428,12 @@ connectOptions2(PGconn *conn) */ if (conn->target_session_attrs) { - if (strcmp(conn->target_session_attrs, "any") != 0 - && strcmp(conn->target_session_attrs, "read-write") != 0) + if (!validateAndGetTargetServerType(conn->target_session_attrs, &conn->requested_server_type)) { 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; } @@ -2192,6 +2218,68 @@ connectDBComplete(PGconn *conn) } } +/* + * Internal helper function used for rejecting (and closing) a connection that + * doesn't satisfy the requested server type (read-write/read-only). + * The connection state is set to try the next host (if any). + */ +static void +rejectCheckedReadOrWriteConnection(PGconn *conn) +{ + if (conn->requested_server_type == SERVER_TYPE_READ_WRITE) + appendPQExpBufferStr(&conn->errorMessage, + libpq_gettext("could not make a writable " + "connection to server\n")); + else + appendPQExpBufferStr(&conn->errorMessage, + libpq_gettext("could not make a readonly " + "connection to server\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; +} + +/* + * Internal helper function used for rejecting (and closing) a connection that + * doesn't satisfy the requested server type (for standby). The connection state + * is set to try the next host (if any). + * In the case of SERVER_TYPE_PREFER_STANDBY, if the primary host-index hasn't + * been set, then it is set to the index of this connection's host, so that a + * connection to this host can be made again in the event that no connection to + * a standby host could be made after the first host scan. + */ +static void +rejectCheckedStandbyConnection(PGconn *conn) +{ + if (conn->requested_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); + + /* Record primary host index */ + if (conn->requested_server_type == SERVER_TYPE_PREFER_STANDBY && conn->which_primary_host == -1) + conn->which_primary_host = conn->whichhost; + + /* + * Try next host if any, but we don't want to consider additional + * addresses for this host. + */ + conn->try_next_host = true; +} + /* ---------------- * PQconnectPoll * @@ -2273,6 +2361,7 @@ PQconnectPoll(PGconn *conn) case CONNECTION_CHECK_WRITABLE: case CONNECTION_CONSUME: case CONNECTION_GSS_STARTUP: + case CONNECTION_CHECK_STANDBY: break; default: @@ -2309,13 +2398,33 @@ keep_going: /* We will come back to here until there is if (conn->whichhost + 1 >= conn->nconnhost) { - /* - * Oops, no more hosts. An appropriate error message is already - * set up, so just set the right status. - */ - goto error_return; + if (conn->which_primary_host >= 0) + { + /* + * Getting here means we failed to connect to standby servers + * and should now try to re-connect to a + * previously-connected-to primary server, whose host index is + * recorded in which_primary_host. + */ + conn->whichhost = conn->which_primary_host; + + /* + * Reset the host index value to avoid recursion during the + * second connection attempt. + */ + conn->which_primary_host = -2; + } + else + { + /* + * Oops, no more hosts. An appropriate error message is + * already set up, so just set the right status. + */ + goto error_return; + } } - conn->whichhost++; + else + conn->whichhost++; /* Drop any address info for previous host */ release_conn_addrinfo(conn); @@ -3545,31 +3654,175 @@ 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 (conn->sversion >= 70400 && - conn->target_session_attrs != NULL && - strcmp(conn->target_session_attrs, "read-write") == 0) + if (conn->requested_server_type != SERVER_TYPE_ANY) { /* - * 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 a read-write or read-only connection is required, + * see if we have one. + * + * Servers before 7.4 lack the + * default_transaction_read_only & in_hot_standby GUC, but + * by the same token they don't have any read-only mode, + * so we may just skip the test in that case. */ - 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->requested_server_type == SERVER_TYPE_READ_WRITE || + conn->requested_server_type == SERVER_TYPE_READ_ONLY)) + { + /* + * For servers which don't have + * "default_transaction_read_only" or "in_hot_standby" + * as a GUC_REPORT variable, it in necessary to + * determine if they are read-only by sending the + * query "SHOW transaction_read_only". + */ + if (conn->default_transaction_read_only == GUC_BOOL_UNKNOWN || + conn->in_hot_standby == GUC_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; + + conn->status = CONNECTION_CHECK_WRITABLE; + return PGRES_POLLING_READING; + } + else if (((conn->default_transaction_read_only == GUC_BOOL_YES || + conn->in_hot_standby == GUC_BOOL_YES) && + conn->requested_server_type == SERVER_TYPE_READ_WRITE) || + (conn->default_transaction_read_only == GUC_BOOL_NO && + conn->in_hot_standby == GUC_BOOL_NO && + conn->requested_server_type == SERVER_TYPE_READ_ONLY)) + { + /* + * Server is read-only but requested read-write, + * or server is read-write but requested + * read-only, reject and continue to process any + * further hosts ... + */ + rejectCheckedReadOrWriteConnection(conn); + goto keep_going; + } + + /* obtained the requested type, consume it */ + goto consume_checked_target_connection; + } + + /* + * Servers before 9.0 don't support standby mode, skip the + * check when the requested type of connection is primary, + * prefer-standby or standby. + */ + else if ((conn->sversion >= 90000 && + (conn->requested_server_type == SERVER_TYPE_PRIMARY || + conn->requested_server_type == SERVER_TYPE_PREFER_STANDBY || + conn->requested_server_type == SERVER_TYPE_STANDBY))) + { + /* + * For servers which don't have the "in_hot_standby" + * GUC_REPORT variable, it in necessary to determine + * if they are in hot standby mode by sending the + * query "SELECT pg_is_in_recovery()". + */ + if (conn->in_hot_standby == GUC_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_is_in_recovery()")) + goto error_return; + + conn->status = CONNECTION_CHECK_STANDBY; + return PGRES_POLLING_READING; + } + else if ((conn->in_hot_standby == GUC_BOOL_YES && + conn->requested_server_type == SERVER_TYPE_PRIMARY) || + (conn->in_hot_standby == GUC_BOOL_NO && + (conn->requested_server_type == SERVER_TYPE_PREFER_STANDBY || + conn->requested_server_type == SERVER_TYPE_STANDBY))) + { + /* + * Server is in standby but requested primary, or + * server is not in standby but requested + * prefer-standby/standby, reject and continue to + * process any further hosts ... + */ + if (conn->which_primary_host == -2) + { + /* + * This scenario is possible only for the + * prefer-standby type for the next pass of + * the list of connections, as it couldn't + * find any servers that are in standby mode. + */ + goto consume_checked_target_connection; + } + + rejectCheckedStandbyConnection(conn); + goto keep_going; + } + + /* obtained the requested type, consume it */ + goto consume_checked_target_connection; + } + + /* + * If the requested type is prefer-standby, then record + * this host index and try any others before considering + * it later. If the requested type of connection is + * read-only or standby, ignore this connection, as + * servers of this version don't support this type of + * connection. + */ + if (conn->requested_server_type == SERVER_TYPE_PREFER_STANDBY || + conn->requested_server_type == SERVER_TYPE_READ_ONLY || + conn->requested_server_type == SERVER_TYPE_STANDBY) + { + if (conn->which_primary_host == -2) + { + /* + * This scenario is possible only for the + * prefer-standby type for the next pass of the + * list of connections, as it couldn't find any + * servers that are in standby mode. + */ + goto consume_checked_target_connection; + } + + /* Close connection politely. */ + conn->status = CONNECTION_OK; + sendTerminateConn(conn); + + /* Record host index */ + if (conn->requested_server_type == SERVER_TYPE_PREFER_STANDBY) + { + if (conn->which_primary_host == -1) + conn->which_primary_host = conn->whichhost; + } + + /* + * Try the next host, if any, but we don't want to + * consider additional addresses for this host. + */ + conn->try_next_host = true; + goto keep_going; + } } + consume_checked_target_connection: + /* We can release the address list now. */ release_conn_addrinfo(conn); @@ -3641,6 +3894,7 @@ keep_going: /* We will come back to here until there is conn->status = CONNECTION_OK; return PGRES_POLLING_OK; } + case CONNECTION_CHECK_WRITABLE: { conn->status = CONNECTION_OK; @@ -3658,30 +3912,112 @@ keep_going: /* We will come back to here until there is PQntuples(res) == 1) { char *val; + bool readonly_server; val = PQgetvalue(res, 0, 0); - if (strncmp(val, "on", 2) == 0) + readonly_server = (strncmp(val, "on", 2) == 0); + + /* + * Server is read-only and requested server type is + * read-write, ignore this connection. Server is + * read-write and requested type is read-only, ignore this + * connection. + */ + if ((readonly_server && + (conn->requested_server_type == SERVER_TYPE_READ_WRITE)) || + (!readonly_server && + (conn->requested_server_type == SERVER_TYPE_READ_ONLY))) { - /* Not writable; fail this connection. */ + /* Not a requested type; fail this connection. */ PQclear(res); + rejectCheckedReadOrWriteConnection(conn); + goto keep_going; + } + /* Session is requested type, so we're good. */ + PQclear(res); - /* Append error report to conn->errorMessage. */ - appendPQExpBufferStr(&conn->errorMessage, - libpq_gettext("session is read-only\n")); + /* + * Finish reading any remaining messages before being + * considered as ready. + */ + conn->status = CONNECTION_CONSUME; + goto keep_going; + } - /* Close connection politely. */ - conn->status = CONNECTION_OK; - sendTerminateConn(conn); + /* + * Something went wrong with "SHOW transaction_read_only". We + * should try next addresses. + */ + if (res) + PQclear(res); + /* Append error report to conn->errorMessage. */ + appendPQExpBufferStr(&conn->errorMessage, + libpq_gettext("test \"SHOW transaction_read_only\" failed\n")); + + /* Close connection politely. */ + conn->status = CONNECTION_OK; + sendTerminateConn(conn); + + /* Try next address */ + conn->try_next_addr = true; + goto keep_going; + } + + case CONNECTION_CHECK_STANDBY: + { + 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; + bool standby_server; + + val = PQgetvalue(res, 0, 0); + standby_server = (strncmp(val, "t", 1) == 0); + + /* + * Server is in standby mode and requested mode is + * primary, ignore it. Server is not in standby mode and + * requested mode is prefer-standby, record it for the + * first time and try to consume in the next scan (it + * means no standby server was found in the first scan). + */ + if ((standby_server && + conn->requested_server_type == SERVER_TYPE_PRIMARY) || + (!standby_server && + (conn->requested_server_type == SERVER_TYPE_PREFER_STANDBY || + conn->requested_server_type == SERVER_TYPE_STANDBY))) + { /* - * Try next host if any, but we don't want to consider - * additional addresses for this host. + * The following scenario is possible only for the + * prefer-standby mode for the next pass of the list + * of connections, as it couldn't find any servers + * that are in standby mode. */ - conn->try_next_host = true; + if (conn->which_primary_host == -2) + goto consume_checked_standby_connection; + + /* Not a requested type; fail this connection. */ + PQclear(res); + + rejectCheckedStandbyConnection(conn); goto keep_going; } - - /* Session is read-write, so we're good. */ + consume_checked_standby_connection: + /* Session is requested type, so we're good. */ PQclear(res); /* @@ -3693,7 +4029,7 @@ keep_going: /* We will come back to here until there is } /* - * Something went wrong with "SHOW transaction_read_only". We + * Something went wrong with "SELECT pg_is_in_recovery()". We * should try next addresses. */ if (res) @@ -3701,7 +4037,7 @@ keep_going: /* We will come back to here until there is /* Append error report to conn->errorMessage. */ appendPQExpBufferStr(&conn->errorMessage, - libpq_gettext("test \"SHOW transaction_read_only\" failed\n")); + libpq_gettext("test \"SELECT pg_is_in_recovery()\" failed\n")); /* Close connection politely. */ conn->status = CONNECTION_OK; @@ -3711,7 +4047,6 @@ keep_going: /* We will come back to here until there is conn->try_next_addr = true; goto keep_going; } - default: appendPQExpBuffer(&conn->errorMessage, libpq_gettext("invalid connection state %d, " @@ -3855,10 +4190,15 @@ 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 = GUC_BOOL_UNKNOWN; + conn->in_hot_standby = GUC_BOOL_UNKNOWN; conn->verbosity = PQERRORS_DEFAULT; conn->show_context = PQSHOW_CONTEXT_ERRORS; conn->sock = PGINVALID_SOCKET; + conn->requested_server_type = SERVER_TYPE_ANY; + conn->which_primary_host = -1; + /* * We try to send at least 8K at a time, which is the usual size of pipe * buffers on Unix systems. That way, when we are sending a large amount diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index e730753..60cd4a7 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -1008,11 +1008,11 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value) } /* - * Special hacks: remember client_encoding and - * standard_conforming_strings, and convert server version to a numeric - * form. We keep the first two of these in static variables as well, so - * that PQescapeString and PQescapeBytea can behave somewhat sanely (at - * least in single-connection-using programs). + * Special hacks: remember client_encoding, default_transaction_read_only, + * in_hot_standby 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). */ if (strcmp(name, "client_encoding") == 0) { @@ -1062,6 +1062,14 @@ 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 ? GUC_BOOL_YES : GUC_BOOL_NO); + } + else if (strcmp(name, "in_hot_standby") == 0) + { + conn->in_hot_standby = (strcmp(value, "on") == 0 ? GUC_BOOL_YES : GUC_BOOL_NO); + } } diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index c266ad5..5c1b349 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -68,7 +68,8 @@ typedef enum CONNECTION_CONSUME, /* Wait for any pending message and consume * them. */ CONNECTION_GSS_STARTUP, /* Negotiating GSSAPI. */ - CONNECTION_CHECK_TARGET /* Check if we have a proper target connection */ + CONNECTION_CHECK_TARGET, /* Check if we have a proper target connection */ + CONNECTION_CHECK_STANDBY /* Check whether server is in standby mode */ } ConnStatusType; typedef enum diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 4db4983..0ba81ae 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -317,6 +317,29 @@ typedef struct pg_conn_host * found in password file. */ } pg_conn_host; +/* Target server type to connect to */ +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_PREFER_STANDBY, /* Prefer Standby server */ + SERVER_TYPE_STANDBY /* Standby server */ +} TargetServerType; + +/* + * State of certain bool GUCs used by libpq, which are determined + * either by the GUC_REPORT mechanism (where supported by the server + * version) or by lazy evaluation (using a query sent to the server). + */ +typedef enum +{ + GUC_BOOL_UNKNOWN = 0, /* Currently unknown */ + GUC_BOOL_YES, /* Yes (true) */ + GUC_BOOL_NO /* No (false) */ +} GucBoolState; + /* * PGconn stores all the state data associated with a single connection * to a backend. @@ -370,9 +393,17 @@ struct pg_conn 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. */ + /* + * Type of connection to make. Possible values: "any", "read-write", + * "read-only", "primary", "prefer-standby", "standby". + */ char *target_session_attrs; + /* + * The requested server type, derived from target_session_attrs. + */ + TargetServerType requested_server_type; + /* Optional file to write trace info to */ FILE *Pfdebug; @@ -406,6 +437,21 @@ struct pg_conn pg_conn_host *connhost; /* details about each named host */ char *connip; /* IP address for current network connection */ + /* + * Index of the first primary host encountered (if any) in the connection + * string. This is used during processing of requested server connection + * type SERVER_TYPE_PREFER_STANDBY. + * + * The initial value is -1, indicating that no primary host has yet been + * found. It is then set to the index of the first primary host, if one is + * found in the connection string during processing. If a second + * connection attempt is later made to that primary host (because no + * connection to a standby server could be made), which_primary_host is + * then set to -2 to avoid recursion during subsequent processing (and + * whichhost is set to the primary host index). + */ + int which_primary_host; + /* Connection data */ pgsocket sock; /* FD for socket, PGINVALID_SOCKET if * unconnected */ @@ -436,6 +482,10 @@ struct pg_conn pgParameterStatus *pstatus; /* ParameterStatus data */ int client_encoding; /* encoding id */ bool std_strings; /* standard_conforming_strings */ + GucBoolState default_transaction_read_only; /* default_transaction_read_only + * GUC report variable state */ + GucBoolState in_hot_standby; /* in_hot_standby GUC report variable + * state */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ diff --git a/src/test/recovery/t/001_stream_rep.pl b/src/test/recovery/t/001_stream_rep.pl index 9e31a53..15d0273 100644 --- a/src/test/recovery/t/001_stream_rep.pl +++ b/src/test/recovery/t/001_stream_rep.pl @@ -3,7 +3,7 @@ use strict; use warnings; use PostgresNode; use TestLib; -use Test::More tests => 36; +use Test::More tests => 49; # Initialize primary node my $node_primary = get_new_node('primary'); @@ -85,7 +85,7 @@ sub test_target_session_attrs my $node2_port = $node2->port; my $node2_name = $node2->name; - my $target_name = $target_node->name; + my $target_name = $target_node->name if (defined $target_node); # Build connection string for connection attempt. my $connstr = "host=$node1_host,$node2_host "; @@ -97,10 +97,25 @@ sub test_target_session_attrs my ($ret, $stdout, $stderr) = $node1->psql('postgres', 'SHOW port;', extra_params => [ '-d', $connstr ]); - is( $status == $ret && $stdout eq $target_node->port, - 1, - "connect to node $target_name if mode \"$mode\" and $node1_name,$node2_name listed" - ); + if ($status == 0) + { + is( $status == $ret && $stdout eq $target_node->port, + 1, + "connect to node $target_name if mode \"$mode\" and $node1_name,$node2_name listed" + ); + } + else + { + print "status = $status\n"; + print "ret = $ret\n"; + print "stdout = $stdout\n"; + print "stderr = $stderr\n"; + + is( $status == $ret, + 1, + "fail to connect to any nodes if mode \"$mode\" and $node1_name,$node2_name listed" + ); + } return; } @@ -121,6 +136,58 @@ test_target_session_attrs($node_primary, $node_standby_1, $node_primary, "any", 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"; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 1d540fe..4a9cc23 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -950,6 +950,7 @@ GroupingSetsPath GucAction GucBoolAssignHook GucBoolCheckHook +GucBoolState GucContext GucEnumAssignHook GucEnumCheckHook @@ -2512,6 +2513,7 @@ TapeShare TarMethodData TarMethodFile TargetEntry +TargetServerType TclExceptionNameMap Tcl_DString Tcl_FileProc -- 1.8.3.1