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,18 +1877,75 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
<term><literal>target_session_attrs</literal></term>
<listitem>
<para>
If this parameter is set to <literal>read-write</literal>, only a
connection in which read-write transactions are accepted by default
is considered acceptable. The query
<literal>SHOW transaction_read_only</literal> will be sent upon any
successful connection; if it returns <literal>on</literal>, the connection
will be closed. If multiple hosts were specified in the connection
string, any remaining servers will be tried just as if the connection
attempt had failed. The default value of this parameter,
<literal>any</literal>, regards all connections as acceptable.
</para>
This option determines whether the session must have certain
properties to be acceptable. It's typically used in combination
with multiple host names to select the first acceptable alternative
among several hosts. There are six modes:
<variablelist>
<varlistentry>
<term><literal>any</literal> (default)</term>
<listitem>
<para>
any successful connection is acceptable
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>read-write</literal></term>
<listitem>
<para>
session must accept read-write transactions by default (that
is, the server must not be in hot standby mode and
the <varname>default_transaction_read_only</varname> parameter
must be <literal>off</literal>)
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>read-only</literal></term>
<listitem>
<para>
session must not accept read-write transactions by default (the
converse)
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>primary</literal></term>
<listitem>
<para>
server must not be in hot standby mode
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>standby</literal></term>
<listitem>
<para>
server must be in hot standby mode
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>prefer-standby</literal></term>
<listitem>
<para>
first try to find a standby server, but if none of the listed
hosts is a standby server, try again in <literal>all</literal>
mode
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
</listitem>
</varlistentry>
</varlistentry>
</variablelist>
</para>
</sect2>
......
This diff is collapsed.
......@@ -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;
}
}
......
......@@ -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
......
......@@ -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 */
......
......@@ -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";
......
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