diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index 02884ba..d263aab 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -1500,13 +1500,23 @@ 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. The query + 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. + attempt had failed. + + + If this paramete is set to prefer_read + the libpq will try to connect to a read-only transactions supported server + firstly. If failed to connect to a read-only transactions supported server + then the libpq will try to connect to a read-write transactions supported + server. + + + The default value of this parameter,any, regards all + connections as acceptable. diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 77eebb0..3fd4c0f 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -327,7 +327,7 @@ static const internalPQconninfoOption PQconninfoOptions[] = { {"target_session_attrs", "PGTARGETSESSIONATTRS", DefaultTargetSessionAttrs, NULL, - "Target-Session-Attrs", "", 11, /* sizeof("read-write") = 11 */ + "Target-Session-Attrs", "", 12, /* sizeof("prefer-read") = 12 */ offsetof(struct pg_conn, target_session_attrs)}, /* Terminating entry --- MUST BE LAST */ @@ -1184,7 +1184,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, "prefer-read") != 0) { conn->status = CONNECTION_BAD; printfPQExpBuffer(&conn->errorMessage, @@ -2086,8 +2087,22 @@ keep_going: /* We will come back to here until there is { if (++conn->whichhost >= conn->nconnhost) { - conn->whichhost = 0; - break; + if (conn->primary_host_index > 0 && + strcmp(conn->target_session_attrs,"prefer-read") ==0 ) + { + /* + * Go to here means failed to connect to read-only servers + * and now try connect to read-write server again. + * Only under the 'prefer-read' scenario will go to here. + */ + conn->addr_cur = conn->connhost[conn->primary_host_index].addrlist; + conn->whichhost = conn->primary_host_index; + } + else + { + conn->whichhost = 0; + break; + } } conn->addr_cur = conn->connhost[conn->whichhost].addrlist; @@ -2341,6 +2356,14 @@ keep_going: /* We will come back to here until there is conn->status = CONNECTION_NEEDED; goto keep_going; } + else if (conn->primary_host_index >= 0 && + strcmp(conn->target_session_attrs, "prefer-read") == 0) + { + conn->addr_cur = conn->connhost[conn->primary_host_index].addrlist; + conn->whichhost = conn->primary_host_index; + conn->status = CONNECTION_NEEDED; + goto keep_going; + } goto error_return; } @@ -2978,10 +3001,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 read-write or prefer-read 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, "read-write") == 0 || + strcmp(conn->target_session_attrs, "prefer-read") == 0)) { /* * We are yet to make a connection. Save all existing @@ -3042,10 +3067,12 @@ keep_going: /* We will come back to here until there is } /* - * If a read-write connection is requested check for same. + * If a read-write or prefer-read connection is requested + * check for same. */ if (conn->target_session_attrs != NULL && - strcmp(conn->target_session_attrs, "read-write") == 0) + (strcmp(conn->target_session_attrs, "read-write") == 0 || + strcmp(conn->target_session_attrs, "prefer-read") == 0)) { if (!saveErrorMessage(conn, &savedMessage)) goto error_return; @@ -3124,57 +3151,130 @@ keep_going: /* We will come back to here until there is PQntuples(res) == 1) { char *val; + bool readonly_server = false; val = PQgetvalue(res, 0, 0); if (strncmp(val, "on", 2) == 0) + readonly_server = true; + + if (strcmp(conn->target_session_attrs, "read-write") == 0) { - const char *displayed_host; - const char *displayed_port; + if(readonly_server) + { + if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS) + displayed_host = conn->connhost[conn->whichhost].hostaddr; + else + displayed_host = conn->connhost[conn->whichhost].host; + displayed_port = conn->connhost[conn->whichhost].port; + if (displayed_port == NULL || displayed_port[0] == '\0') + displayed_port = DEF_PGPORT_STR; - if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS) - displayed_host = conn->connhost[conn->whichhost].hostaddr; - else - displayed_host = conn->connhost[conn->whichhost].host; - displayed_port = conn->connhost[conn->whichhost].port; - if (displayed_port == NULL || displayed_port[0] == '\0') - displayed_port = DEF_PGPORT_STR; + PQclear(res); + restoreErrorMessage(conn, &savedMessage); - PQclear(res); - restoreErrorMessage(conn, &savedMessage); + /* Not writable; close connection. */ + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("could not make a writable " + "connection to server " + "\"%s:%s\"\n"), + displayed_host, displayed_port); + conn->status = CONNECTION_OK; + sendTerminateConn(conn); + pqDropConnection(conn, true); - /* Not writable; close connection. */ - appendPQExpBuffer(&conn->errorMessage, - libpq_gettext("could not make a writable " - "connection to server " - "\"%s:%s\"\n"), - displayed_host, displayed_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; + } - /* Skip any remaining addresses for this host. */ - conn->addr_cur = NULL; - if (conn->whichhost + 1 < conn->nconnhost) + /* No more addresses to try. So we fail. */ + goto error_return; + } + else /* server support read-write */ { - conn->status = CONNECTION_NEEDED; + PQclear(res); + termPQExpBuffer(&savedMessage); + + /* We can release the address lists now. */ + release_all_addrinfo(conn); + + /* + * Finish reading any remaining messages before being + * considered as ready. + */ + conn->status = CONNECTION_CONSUME; goto keep_going; } - - /* No more addresses to try. So we fail. */ - goto error_return; } - PQclear(res); - termPQExpBuffer(&savedMessage); + else /* conn->target_session_attrs is prefer-read */ + { + if(readonly_server) + { + PQclear(res); + termPQExpBuffer(&savedMessage); - /* We can release the address lists now. */ - release_all_addrinfo(conn); + /* We can release the address lists now. */ + release_all_addrinfo(conn); - /* - * Finish reading any remaining messages before being - * considered as ready. - */ - conn->status = CONNECTION_CONSUME; - goto keep_going; + /* + * Finish reading any remaining messages before being + * considered as ready. + */ + conn->status = CONNECTION_CONSUME; + goto keep_going; + } + else /* server support read-write */ + { + if ((conn->primary_host_index < 0) && (conn->whichhost + 1 < conn->nconnhost)) + { + if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS) + displayed_host = conn->connhost[conn->whichhost].hostaddr; + else + displayed_host = conn->connhost[conn->whichhost].host; + displayed_port = conn->connhost[conn->whichhost].port; + if (displayed_port == NULL || displayed_port[0] == '\0') + displayed_port = DEF_PGPORT_STR; + + PQclear(res); + restoreErrorMessage(conn, &savedMessage); + + /* + * Connecting to a writable server, close it + * and try to connect to another one. + */ + conn->status = CONNECTION_OK; + sendTerminateConn(conn); + pqDropConnection(conn, true); + + /* Skip any remaining addresses for this host. */ + conn->addr_cur = NULL; + + conn->status = CONNECTION_NEEDED; + + /* Record primary host index */ + conn->primary_host_index = conn->whichhost; + goto keep_going; + } + else /* No more host to connect, keep this connection */ + { + PQclear(res); + termPQExpBuffer(&savedMessage); + + /* We can release the address lists now. */ + release_all_addrinfo(conn); + + /* + * Finish reading any remaining messages before being + * considered as ready. + */ + conn->status = CONNECTION_CONSUME; + goto keep_going; + } + } + } } /* @@ -3393,6 +3493,8 @@ makeEmptyPGconn(void) conn = NULL; } + conn->primary_host_index = -1; + return conn; } diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 4e35409..a3e582d 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -362,7 +362,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, perfer-read. */ char *target_session_attrs; /* Optional file to write trace info to */ @@ -396,6 +396,7 @@ struct pg_conn int nconnhost; /* # of possible hosts */ int whichhost; /* host we're currently considering */ pg_conn_host *connhost; /* details about each possible host */ + int primary_host_index; /* index for primary host in connhost */ /* Connection data */ pgsocket sock; /* FD for socket, PGINVALID_SOCKET if diff --git a/src/test/recovery/t/001_stream_rep.pl b/src/test/recovery/t/001_stream_rep.pl index fb27925..09816e0 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 => 28; +use Test::More tests => 31; # Initialize master node my $node_master = get_new_node('master'); @@ -115,6 +115,18 @@ test_target_session_attrs($node_master, $node_standby_1, $node_master, "any", test_target_session_attrs($node_standby_1, $node_master, $node_standby_1, "any", 0); +# Connect to standby1 in "prefer-read" mode with master,standby1 list. +test_target_session_attrs($node_master, $node_standby_1, $node_standby_1, "prefer-read", + 0); + +# Connect to standby1 in "prefer-read" mode with standby1,master list. +test_target_session_attrs($node_standby_1, $node_master, $node_standby_1, + "prefer-read", 0); + +# Connect to node_master in "prefer-read" mode with only master list. +test_target_session_attrs($node_master, $node_master, $node_master, + "prefer-read", 0); + note "switching to physical replication slot"; # Switch to using a physical replication slot. We can do this without a new