diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 493cc12..b359333 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1425,10 +1425,14 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname If this parameter is set to read-write, only a connection in which read-write transactions are accepted by default + is considered acceptable. + If this parameter is set to read-only, only a + connection in which read-write transactions are rejected 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 + successful connection if the server is prior to version 10. + If the session attribute does not match the requested one, 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. @@ -1709,6 +1713,7 @@ const char *PQparameterStatus(const PGconn *conn, const char *paramName); application_name, is_superuser, session_authorization, + session_read_only, DateStyle, IntervalStyle, TimeZone, @@ -1719,7 +1724,8 @@ const char *PQparameterStatus(const PGconn *conn, const char *paramName); standard_conforming_strings was not reported by releases before 8.1; IntervalStyle was not reported by releases before 8.4; - application_name was not reported by releases before 9.0.) + application_name was not reported by releases before 9.0; + session_read_only was not reported by releases before 10.0.) Note that server_version, server_encoding and diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index d23df02..291d564 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -1136,6 +1136,7 @@ application_name, is_superuser, session_authorization, + session_read_only, DateStyle, IntervalStyle, TimeZone, @@ -1146,7 +1147,8 @@ standard_conforming_strings was not reported by releases before 8.1; IntervalStyle was not reported by releases before 8.4; - application_name was not reported by releases before 9.0.) + application_name was not reported by releases before 9.0; + session_read_only was not reported by releases before 10.0.) Note that server_version, server_encoding and diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 399822d..bf0bae8 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -7904,6 +7904,10 @@ RecoveryInProgress(void) */ pg_memory_barrier(); InitXLOGAccess(); + + /* Update session read-only status. */ + SetConfigOption("session_read_only", DefaultXactReadOnly, + PGC_INTERNAL, PGC_S_OVERRIDE); } /* diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 75c2d9a..8933f4f 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -3722,6 +3722,14 @@ PostgresMain(int argc, char *argv[], InitPostgres(dbname, InvalidOid, username, InvalidOid, NULL); /* + * Update session read-only status if in recovery. + */ + if (IsUnderPostmaster && !DefaultXactReadOnly && + RecoveryInProgress()) + SetConfigOption("session_read_only", "on", + PGC_INTERNAL, PGC_S_OVERRIDE); + + /* * If the PostmasterContext is still around, recycle the space; we don't * need it anymore after InitPostgres completes. Note this does not trash * *MyProcPort, because ConnCreate() allocated that space with malloc() diff --git a/src/backend/utils/misc/check_guc b/src/backend/utils/misc/check_guc index d228bbe..da96df2 100755 --- a/src/backend/utils/misc/check_guc +++ b/src/backend/utils/misc/check_guc @@ -21,7 +21,7 @@ is_superuser lc_collate lc_ctype lc_messages lc_monetary lc_numeric lc_time \ pre_auth_delay role seed server_encoding server_version server_version_int \ session_authorization trace_lock_oidmin trace_lock_table trace_locks trace_lwlocks \ trace_notify trace_userlocks transaction_isolation transaction_read_only \ -zero_damaged_pages" +zero_damaged_pages session_read_only" ### What options are listed in postgresql.conf.sample, but don't appear ### in guc.c? diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 92e1d63..2aff993 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -147,6 +147,8 @@ static bool call_string_check_hook(struct config_string * conf, char **newval, static bool call_enum_check_hook(struct config_enum * conf, int *newval, void **extra, GucSource source, int elevel); +static void assign_default_transaction_read_only(bool newval, void *extra); + static bool check_log_destination(char **newval, void **extra, GucSource source); static void assign_log_destination(const char *newval, void *extra); @@ -493,6 +495,7 @@ int huge_pages; */ static char *syslog_ident_str; static bool session_auth_is_superuser; +static bool session_read_only; static double phony_random_seed; static char *client_encoding_string; static char *datestyle_string; @@ -933,6 +936,16 @@ static struct config_bool ConfigureNamesBool[] = NULL, NULL, NULL }, { + {"session_read_only", PGC_INTERNAL, UNGROUPED, + gettext_noop("Shows whether the session is read-only by default."), + NULL, + GUC_REPORT | GUC_NO_SHOW_ALL | GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE + }, + &session_read_only, + false, + NULL, NULL, NULL + }, + { {"bonjour", PGC_POSTMASTER, CONN_AUTH_SETTINGS, gettext_noop("Enables advertising the server via Bonjour."), NULL @@ -1366,7 +1379,7 @@ static struct config_bool ConfigureNamesBool[] = }, &DefaultXactReadOnly, false, - NULL, NULL, NULL + NULL, assign_default_transaction_read_only, NULL }, { {"transaction_read_only", PGC_USERSET, CLIENT_CONN_STATEMENT, @@ -10038,6 +10051,30 @@ assign_wal_consistency_checking(const char *newval, void *extra) wal_consistency_checking = (bool *) extra; } +static void +assign_default_transaction_read_only(bool newval, void *extra) +{ + if (newval == DefaultXactReadOnly) + return; + + /* + * We clamp manually-set values to at least 1MB. Since + * Also set the session read-only parameter. We only need + * to set the correct value in processes that have database + * sessions, but there's no mechanism to know that there's + * a session. Instead, we use the shared memory segment + * pointer because the processes with database sessions are + * attached to the shared memory. Without this check, + * RecoveryInProgress() would crash the processes which + * are not attached to the shared memory. + */ + if (IsUnderPostmaster && UsedShmemSegAddr != NULL && + RecoveryInProgress()) + newval = true; + SetConfigOption("session_read_only", newval ? "on" : "off", + PGC_INTERNAL, PGC_S_OVERRIDE); +} + static bool check_log_destination(char **newval, void **extra, GucSource source) { diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index f2c9bf7..e63e16b 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -1066,7 +1066,8 @@ connectOptions2(PGconn *conn) if (conn->target_session_attrs) { if (strcmp(conn->target_session_attrs, "any") != 0 - && strcmp(conn->target_session_attrs, "read-write") != 0) + && strcmp(conn->target_session_attrs, "read-write") != 0 + && strcmp(conn->target_session_attrs, "read-only") != 0) { conn->status = CONNECTION_BAD; printfPQExpBuffer(&conn->errorMessage, @@ -2848,10 +2849,12 @@ keep_going: /* We will come back to here until there is } /* - * If a read-write connection is required, see if we have one. + * If a specific type of connection is required, see if we have one. */ - if (conn->target_session_attrs != NULL && - strcmp(conn->target_session_attrs, "read-write") == 0) + /* If the server version is before 10.0, issue an SQL query. */ + if (conn->sversion < 100000 && + conn->target_session_attrs != NULL && + strcmp(conn->target_session_attrs, "any") != 0) { /* * We are yet to make a connection. Save all existing @@ -2876,6 +2879,34 @@ keep_going: /* We will come back to here until there is return PGRES_POLLING_READING; } + /* Otherwise, check parameter status sent by backend to avoid round-trip for a query. */ + if (conn->target_session_attrs != NULL && + ((strcmp(conn->target_session_attrs, "read-write") == 0 && !conn->session_read_only) || + (strcmp(conn->target_session_attrs, "read-only") == 0 && conn->session_read_only))) + { + /* Not suitable; close connection. */ + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not make a suitable " + "connection to server " + "\"%s:%s\"\n"), + conn->connhost[conn->whichhost].host, + conn->connhost[conn->whichhost].port); + conn->status = CONNECTION_OK; + sendTerminateConn(conn); + pqDropConnection(conn, true); + + /* Skip any remaining addresses for this host. */ + conn->addr_cur = NULL; + if (conn->whichhost + 1 < conn->nconnhost) + { + conn->status = CONNECTION_NEEDED; + goto keep_going; + } + + /* No more addresses to try. So we fail. */ + goto error_return; + } + /* We can release the address lists now. */ release_all_addrinfo(conn); @@ -2912,10 +2943,10 @@ keep_going: /* We will come back to here until there is } /* - * If a read-write connection is requested check for same. + * If a specific type of connection is required, see if we have one. */ if (conn->target_session_attrs != NULL && - strcmp(conn->target_session_attrs, "read-write") == 0) + strcmp(conn->target_session_attrs, "any") != 0) { if (!saveErrorMessage(conn, &savedMessage)) goto error_return; @@ -2991,16 +3022,29 @@ keep_going: /* We will come back to here until there is PQntuples(res) == 1) { char *val; + char *expected_val; + int expected_len; + + if (strcmp(conn->target_session_attrs, "read-write") == 0) + { + expected_val = "on"; + expected_len = 2; + } + else + { + expected_val = "off"; + expected_len = 3; + } val = PQgetvalue(res, 0, 0); - if (strncmp(val, "on", 2) == 0) + if (strncmp(val, expected_val, expected_len) == 0) { PQclear(res); restoreErrorMessage(conn, &savedMessage); - /* Not writable; close connection. */ + /* Not suitable; close connection. */ appendPQExpBuffer(&conn->errorMessage, - libpq_gettext("could not make a writable " + libpq_gettext("could not make a suitable " "connection to server " "\"%s:%s\"\n"), conn->connhost[conn->whichhost].host, @@ -3200,6 +3244,7 @@ makeEmptyPGconn(void) conn->setenv_state = SETENV_STATE_IDLE; conn->client_encoding = PG_SQL_ASCII; conn->std_strings = false; /* unless server says differently */ + conn->session_read_only = false; /* unless server says differently */ 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 9decd53..9b034aa 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -956,8 +956,8 @@ 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 + * Special hacks: remember client_encoding/ + * standard_conforming_strings/session_read_only, 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). @@ -975,6 +975,8 @@ pqSaveParameterStatus(PGconn *conn, const char *name, const char *value) conn->std_strings = (strcmp(value, "on") == 0); static_std_strings = conn->std_strings; } + else if (strcmp(name, "session_read_only") == 0) + conn->session_read_only = (strcmp(value, "on") == 0); else if (strcmp(name, "server_version") == 0) { int cnt; diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 335568b..2430982 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -360,7 +360,7 @@ struct pg_conn char *krbsrvname; /* Kerberos service name */ #endif - /* Type of connection to make. Possible values: any, read-write. */ + /* Type of connection to make. Possible values: any, read-write, read-only. */ char *target_session_attrs; /* Optional file to write trace info to */ @@ -422,6 +422,7 @@ struct pg_conn pgParameterStatus *pstatus; /* ParameterStatus data */ int client_encoding; /* encoding id */ bool std_strings; /* standard_conforming_strings */ + bool session_read_only; /* session_read_only */ PGVerbosity verbosity; /* error/notice message verbosity */ PGContextVisibility show_context; /* whether to show CONTEXT field */ PGlobjfuncs *lobjfuncs; /* private state for large-object access fns */