Commit ee28cacf authored by Tom Lane's avatar Tom Lane

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