From ee28cacf619f4d9c23af5a80e1171a5adae97381 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 2 Mar 2021 20:17:45 -0500 Subject: [PATCH] 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 --- doc/src/sgml/libpq.sgml | 79 +++++- src/interfaces/libpq/fe-connect.c | 352 ++++++++++++++++++++------ src/interfaces/libpq/fe-exec.c | 21 +- src/interfaces/libpq/libpq-fe.h | 9 +- src/interfaces/libpq/libpq-int.h | 27 +- src/test/recovery/t/001_stream_rep.pl | 82 +++++- 6 files changed, 456 insertions(+), 114 deletions(-) diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 42b02b0a00..cc20b0f234 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1877,18 +1877,75 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname target_session_attrs - If this parameter is set to read-write, only a - connection in which read-write transactions are accepted by default - is considered acceptable. The query - SHOW transaction_read_only will be sent upon any - successful connection; if it returns on, the connection - will be closed. If multiple hosts were specified in the connection - string, any remaining servers will be tried just as if the connection - attempt had failed. The default value of this parameter, - any, regards all connections as acceptable. - + This option determines whether the session must have certain + properties to be acceptable. It's typically used in combination + with multiple host names to select the first acceptable alternative + among several hosts. There are six modes: + + + + any (default) + + + any successful connection is acceptable + + + + + + read-write + + + session must accept read-write transactions by default (that + is, the server must not be in hot standby mode and + the default_transaction_read_only parameter + must be off) + + + + + + read-only + + + session must not accept read-write transactions by default (the + converse) + + + + + + primary + + + server must not be in hot standby mode + + + + + + standby + + + server must be in hot standby mode + + + + + + prefer-standby + + + first try to find a standby server, but if none of the listed + hosts is a standby server, try again in all + mode + + + + + - + diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index db71fea169..9812a14662 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -356,7 +356,7 @@ static const internalPQconninfoOption PQconninfoOptions[] = { {"target_session_attrs", "PGTARGETSESSIONATTRS", DefaultTargetSessionAttrs, NULL, - "Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */ + "Target-Session-Attrs", "", 15, /* sizeof("prefer-standby") = 15 */ offsetof(struct pg_conn, target_session_attrs)}, /* Terminating entry --- MUST BE LAST */ @@ -583,6 +583,8 @@ pqDropServerData(PGconn *conn) conn->pstatus = NULL; conn->client_encoding = PG_SQL_ASCII; conn->std_strings = false; + conn->default_transaction_read_only = PG_BOOL_UNKNOWN; + conn->in_hot_standby = PG_BOOL_UNKNOWN; conn->sversion = 0; /* Drop large-object lookup data */ @@ -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; diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index e730753387..a550753855 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -1008,11 +1008,11 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value) } /* - * Special hacks: remember client_encoding and - * standard_conforming_strings, and convert server version to a numeric - * form. We keep the first two of these in static variables as well, so - * that PQescapeString and PQescapeBytea can behave somewhat sanely (at - * least in single-connection-using programs). + * Save values of settings that are of interest to libpq in fields of the + * PGconn object. We keep client_encoding and standard_conforming_strings + * in static variables as well, so that PQescapeString and PQescapeBytea + * can behave somewhat sanely (at least in single-connection-using + * programs). */ if (strcmp(name, "client_encoding") == 0) { @@ -1029,6 +1029,7 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value) } else if (strcmp(name, "server_version") == 0) { + /* We convert the server version to numeric form. */ int cnt; int vmaj, vmin, @@ -1062,6 +1063,16 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value) else conn->sversion = 0; /* unknown */ } + else if (strcmp(name, "default_transaction_read_only") == 0) + { + conn->default_transaction_read_only = + (strcmp(value, "on") == 0) ? PG_BOOL_YES : PG_BOOL_NO; + } + else if (strcmp(name, "in_hot_standby") == 0) + { + conn->in_hot_standby = + (strcmp(value, "on") == 0) ? PG_BOOL_YES : PG_BOOL_NO; + } } diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index effe0ccf85..47a098b4b9 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -63,12 +63,11 @@ typedef enum CONNECTION_SETENV, /* Negotiating environment. */ CONNECTION_SSL_STARTUP, /* Negotiating SSL. */ CONNECTION_NEEDED, /* Internal state: connect() needed */ - CONNECTION_CHECK_WRITABLE, /* Check if we could make a writable - * connection. */ - CONNECTION_CONSUME, /* Wait for any pending message and consume - * them. */ + CONNECTION_CHECK_WRITABLE, /* Checking if session is read-write. */ + CONNECTION_CONSUME, /* Consuming any extra messages. */ CONNECTION_GSS_STARTUP, /* Negotiating GSSAPI. */ - CONNECTION_CHECK_TARGET /* Check if we have a proper target connection */ + CONNECTION_CHECK_TARGET, /* Checking target server properties. */ + CONNECTION_CHECK_STANDBY /* Checking if server is in standby mode. */ } ConnStatusType; typedef enum diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index ce36aabd25..0c9e95f1a7 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -232,6 +232,26 @@ typedef enum PGQUERY_DESCRIBE /* Describe Statement or Portal */ } PGQueryClass; +/* Target server type (decoded value of target_session_attrs) */ +typedef enum +{ + SERVER_TYPE_ANY = 0, /* Any server (default) */ + SERVER_TYPE_READ_WRITE, /* Read-write server */ + SERVER_TYPE_READ_ONLY, /* Read-only server */ + SERVER_TYPE_PRIMARY, /* Primary server */ + SERVER_TYPE_STANDBY, /* Standby server */ + SERVER_TYPE_PREFER_STANDBY, /* Prefer standby server */ + SERVER_TYPE_PREFER_STANDBY_PASS2 /* second pass - behaves same as ANY */ +} PGTargetServerType; + +/* Boolean value plus a not-known state, for GUCs we might have to fetch */ +typedef enum +{ + PG_BOOL_UNKNOWN = 0, /* Currently unknown */ + PG_BOOL_YES, /* Yes (true) */ + PG_BOOL_NO /* No (false) */ +} PGTernaryBool; + /* PGSetenvStatusType defines the state of the pqSetenv state machine */ /* (this is used only for 2.0-protocol connections) */ @@ -370,9 +390,7 @@ struct pg_conn * "sspi") */ char *ssl_min_protocol_version; /* minimum TLS protocol version */ char *ssl_max_protocol_version; /* maximum TLS protocol version */ - - /* Type of connection to make. Possible values: any, read-write. */ - char *target_session_attrs; + char *target_session_attrs; /* desired session properties */ /* Optional file to write trace info to */ FILE *Pfdebug; @@ -422,6 +440,7 @@ struct pg_conn char *write_err_msg; /* write error message, or NULL if OOM */ /* Transient state needed while establishing connection */ + PGTargetServerType target_server_type; /* desired session properties */ bool try_next_addr; /* time to advance to next address/host? */ bool try_next_host; /* time to advance to next connhost[]? */ struct addrinfo *addrlist; /* list of addresses for current connhost */ @@ -437,6 +456,8 @@ struct pg_conn pgParameterStatus *pstatus; /* ParameterStatus data */ int client_encoding; /* encoding id */ bool std_strings; /* standard_conforming_strings */ + PGTernaryBool default_transaction_read_only; /* default_transaction_read_only */ + PGTernaryBool in_hot_standby; /* in_hot_standby */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */ diff --git a/src/test/recovery/t/001_stream_rep.pl b/src/test/recovery/t/001_stream_rep.pl index 9e31a53de7..07a9912ce2 100644 --- a/src/test/recovery/t/001_stream_rep.pl +++ b/src/test/recovery/t/001_stream_rep.pl @@ -3,7 +3,7 @@ use strict; use warnings; use PostgresNode; use TestLib; -use Test::More tests => 36; +use Test::More tests => 49; # Initialize primary node my $node_primary = get_new_node('primary'); @@ -85,7 +85,7 @@ sub test_target_session_attrs my $node2_port = $node2->port; my $node2_name = $node2->name; - my $target_name = $target_node->name; + my $target_name = $target_node->name if (defined $target_node); # Build connection string for connection attempt. my $connstr = "host=$node1_host,$node2_host "; @@ -97,10 +97,25 @@ sub test_target_session_attrs my ($ret, $stdout, $stderr) = $node1->psql('postgres', 'SHOW port;', extra_params => [ '-d', $connstr ]); - is( $status == $ret && $stdout eq $target_node->port, - 1, - "connect to node $target_name if mode \"$mode\" and $node1_name,$node2_name listed" - ); + if ($status == 0) + { + is( $status == $ret && $stdout eq $target_node->port, + 1, + "connect to node $target_name if mode \"$mode\" and $node1_name,$node2_name listed" + ); + } + else + { + print "status = $status\n"; + print "ret = $ret\n"; + print "stdout = $stdout\n"; + print "stderr = $stderr\n"; + + is( $status == $ret, + 1, + "fail to connect to any nodes if mode \"$mode\" and $node1_name,$node2_name listed" + ); + } return; } @@ -114,13 +129,64 @@ test_target_session_attrs($node_standby_1, $node_primary, $node_primary, "read-write", 0); # Connect to primary in "any" mode with primary,standby1 list. -test_target_session_attrs($node_primary, $node_standby_1, $node_primary, "any", - 0); +test_target_session_attrs($node_primary, $node_standby_1, $node_primary, + "any", 0); # Connect to standby1 in "any" mode with standby1,primary list. test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1, "any", 0); +# Connect to primary in "primary" mode with primary,standby1 list. +test_target_session_attrs($node_primary, $node_standby_1, $node_primary, + "primary", 0); + +# Connect to primary in "primary" mode with standby1,primary list. +test_target_session_attrs($node_standby_1, $node_primary, $node_primary, + "primary", 0); + +# Connect to standby1 in "read-only" mode with primary,standby1 list. +test_target_session_attrs($node_primary, $node_standby_1, $node_standby_1, + "read-only", 0); + +# Connect to standby1 in "read-only" mode with standby1,primary list. +test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1, + "read-only", 0); + +# Connect to primary in "prefer-standby" mode with primary,primary list. +test_target_session_attrs($node_primary, $node_primary, $node_primary, + "prefer-standby", 0); + +# Connect to standby1 in "prefer-standby" mode with primary,standby1 list. +test_target_session_attrs($node_primary, $node_standby_1, $node_standby_1, + "prefer-standby", 0); + +# Connect to standby1 in "prefer-standby" mode with standby1,primary list. +test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1, + "prefer-standby", 0); + +# Connect to standby1 in "standby" mode with primary,standby1 list. +test_target_session_attrs($node_primary, $node_standby_1, $node_standby_1, + "standby", 0); + +# Connect to standby1 in "standby" mode with standby1,primary list. +test_target_session_attrs($node_standby_1, $node_primary, $node_standby_1, + "standby", 0); + +# Fail to connect in "read-write" mode with standby1,standby2 list. +test_target_session_attrs($node_standby_1, $node_standby_2, undef, + "read-write", 2); + +# Fail to connect in "primary" mode with standby1,standby2 list. +test_target_session_attrs($node_standby_1, $node_standby_2, undef, + "primary", 2); + +# Fail to connect in "read-only" mode with primary,primary list. +test_target_session_attrs($node_primary, $node_primary, undef, + "read-only", 2); + +# Fail to connect in "standby" mode with primary,primary list. +test_target_session_attrs($node_primary, $node_primary, undef, "standby", 2); + # Test for SHOW commands using a WAL sender connection with a replication # role. note "testing SHOW commands for replication connection"; -- 2.39.5