Skip to content

Commit

Permalink
Expose parameter status messages (GUC_REPORT) to the user (#1435)
Browse files Browse the repository at this point in the history
* Expose parameter status messages (GUC_REPORT) to the user

Add a new `Map PGConnection.getParameterStatuses()` method that tracks the
latest values of all `GUC_REPORT` parameters reported by the server in
`ParameterStatus` protocol messages from the server. The map is read-only. A
convenience `PGConnection.getParameterStatus(String)` wrapper is also provided.

This provides a PgJDBC equivalent to the `PQparameterStatus(...)` `libpq` API
function.

Extensions may define custom GUCs that are set as `GUC_REPORT` when they
`DefineCustomStringVariable(...)` etc. This feature will properly handle such
GUCs, allowing applications to generate parameter status change messages in
their extensions and have them available to the JDBC driver without needing
round trips.

No assumptions are made about which server GUCs are `GUC_REPORT` or their
names, so it'll work (with possible test case tweaks) on current and future
server versions.

Github issue #1428

* Attempt to make StatementTest.testShortQueryTimeout() more reliable

I'm observing intermittent failures like:

    testShortQueryTimeout(org.postgresql.test.jdbc2.StatementTest)  Time elapsed: 0.219 sec  <<< ERROR!
    org.postgresql.util.PSQLException: ERROR: canceling statement due to user request

The cause of that isn't yet clear. But I noticed that the test doesn't check
for the SQLSTATE of the expected exception, so fix that.
  • Loading branch information
ringerc authored and davecramer committed Jul 5, 2019
1 parent 3db55da commit ce8333a
Show file tree
Hide file tree
Showing 12 changed files with 443 additions and 8 deletions.
1 change: 1 addition & 0 deletions docs/documentation/head/ext.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ next: geometric.html
* [Large Objects](largeobjects.html)
* [Listen / Notify](listennotify.html)
* [Server Prepared Statements](server-prepare.html)
* [Parameter Status Messages](parameterstatus.html)
* [Physical and Logical replication API](replication.html)
* [Arrays](arrays.html)

Expand Down
63 changes: 63 additions & 0 deletions docs/documentation/head/parameterstatus.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
layout: default_docs
title: Physical and Logical replication API
header: Chapter 9. PostgreSQL™ Extensions to the JDBC API
resource: media
previoustitle: Parameter Status Messages
previous: server-prepare.html
nexttitle: Physical and Logical replication API
next: replication.html
---

# Parameter Status Messages

PostgreSQL supports server parameters, also called server variables or,
internally, Grand Unified Configuration (GUC) variables. These variables are
manipulated by the `SET` command, `postgresql.conf`, `ALTER SYSTEM SET`, `ALTER
USER SET`, `ALTER DATABASE SET`, the `set_config(...)` SQL-callable function,
etc. See [the PostgreSQL manual](https://www.postgresql.org/docs/current/config-setting.html).

For a subset of these variables the server will *automatically report changes
to the value to the client driver and application*. These variables are known
internally as `GUC_REPORT` variables after the name of the flag that enables
the functionality.

The server keeps track of all the variable scopes and reports when a variable
reverts to a prior value, so the client doesn't have to guess what the current
value is and whether some server-side function could've changed it. Whenever
the value changes, no matter why or how it changes, the server reports the new
effective value in a *Parameter Status* protocol message to the client. PgJDBC
uses many of these reports internally.

As of PgJDBC 42.2.6, it also exposes the parameter status information to user
applications via the PGConnection extensions interface.

## Methods

Two methods on `org.postgresql.PGConnection` provide the client interface to
reported parameters. Parameter names are case-insensitive and case-preserving.

* `Map PGConnection.getParameterStatuses()` - return a map of all reported
parameters and their values.

* `String PGConnection.getParameterStatus()` - shorthand to retrieve one
value by name, or null if no value has been reported.

See the `PGConnection` JavaDoc for details.

## Example

If you're working directly with a `java.sql.Connection` you can

import org.postgresql.PGConnection;

void my_function(Connection conn) {

System.out.println("My application name is " +
((PGConnection)conn).getParameterStatus("application_name"));

}

## Other client drivers

The `libpq` equivalent is the `PQparameterStatus(...)` API function.
4 changes: 2 additions & 2 deletions docs/documentation/head/replication.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ layout: default_docs
title: Physical and Logical replication API
header: Chapter 9. PostgreSQL™ Extensions to the JDBC API
resource: media
previoustitle: Server Prepared Statements
previous: server-prepare.html
previoustitle: Parameter Status Messages
previous: parameterstatus.html
nexttitle: Arrays
next: arrays.html
---
Expand Down
4 changes: 2 additions & 2 deletions docs/documentation/head/server-prepare.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ header: Chapter 9. PostgreSQL™ Extensions to the JDBC API
resource: media
previoustitle: Listen / Notify
previous: listennotify.html
nexttitle: Physical and Logical replication API
next: replication.html
nexttitle: Parameter Status Messages
next: parameterstatus.html
---

### Motivation
Expand Down
74 changes: 74 additions & 0 deletions pgjdbc/src/main/java/org/postgresql/PGConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import java.sql.SQLException;
import java.sql.Statement;

import java.util.Map;

/**
* This interface defines the public PostgreSQL extensions to java.sql.Connection. All Connections
* returned by the PostgreSQL driver implement PGConnection.
Expand Down Expand Up @@ -236,4 +238,76 @@ public interface PGConnection {
* @return replication API for the current connection
*/
PGReplicationConnection getReplicationAPI();

/**
* <p>Returns the current values of all parameters reported by the server.</p>
*
* <p>PostgreSQL reports values for a subset of parameters (GUCs) to the client
* at connect-time, then sends update messages whenever the values change
* during a session. PgJDBC records the latest values and exposes it to client
* applications via <code>getParameterStatuses()</code>.</p>
*
* <p>PgJDBC exposes individual accessors for some of these parameters as
* listed below. They are more backwarrds-compatible and should be preferred
* where possible.</p>
*
* <p>Not all parameters are reported, only those marked
* <code>GUC_REPORT</code> in the source code. The <code>pg_settings</code>
* view does not expose information about which parameters are reportable.
* PgJDBC's map will only contain the parameters the server reports values
* for, so you cannot use this method as a substitute for running a
* <code>SHOW paramname;</code> or <code>SELECT
* current_setting('paramname');</code> query for arbitrary parameters.</p>
*
* <p>Parameter names are <i>case-insensitive</i> and <i>case-preserving</i>
* in this map, like in PostgreSQL itself. So <code>DateStyle</code> and
* <code>datestyle</code> are the same key.</p>
*
* <p>
* As of PostgreSQL 11 the reportable parameter list, and related PgJDBC
* interfaces or accesors, are:
* </p>
*
* <ul>
* <li>
* <code>application_name</code> -
* {@link java.sql.Connection#getClientInfo()},
* {@link java.sql.Connection#setClientInfo(java.util.Properties)}
* and <code>ApplicationName</code> connection property.
* </li>
* <li>
* <code>client_encoding</code> - PgJDBC always sets this to <code>UTF8</code>.
* See <code>allowEncodingChanges</code> connection property.
* </li>
* <li><code>DateStyle</code> - PgJDBC requires this to always be set to <code>ISO</code></li>
* <li><code>standard_conforming_strings</code> - indirectly via {@link #escapeLiteral(String)}</li>
* <li>
* <code>TimeZone</code> - set from JDK timezone see {@link java.util.TimeZone#getDefault()}
* and {@link java.util.TimeZone#setDefault(TimeZone)}
* </li>
* <li><code>integer_datetimes</code></li>
* <li><code>IntervalStyle</code></li>
* <li><code>server_encoding</code></li>
* <li><code>server_version</code></li>
* <li><code>is_superuser</code> </li>
* <li><code>session_authorization</code></li>
* </ul>
*
* <p>Note that some PgJDBC operations will change server parameters
* automatically.</p>
*
* @return unmodifiable map of case-insensitive parameter names to parameter values
* @since 42.2.6
*/
Map<String,String> getParameterStatuses();

/**
* Shorthand for getParameterStatuses().get(...) .
*
* @param parameterName case-insensitive parameter name
* @return parameter value if defined, or null if no parameter known
* @see #getParameterStatuses
* @since 42.2.6
*/
String getParameterStatus(String parameterName);
}
6 changes: 6 additions & 0 deletions pgjdbc/src/main/java/org/postgresql/core/QueryExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;

Expand Down Expand Up @@ -452,4 +453,9 @@ Object createQueryKey(String sql, boolean escapeProcessing, boolean isParameteri
void setNetworkTimeout(int milliseconds) throws IOException;

int getNetworkTimeout() throws IOException;

// Expose parameter status to PGConnection
Map<String,String> getParameterStatuses();

String getParameterStatus(String parameterName);
}
45 changes: 45 additions & 0 deletions pgjdbc/src/main/java/org/postgresql/core/QueryExecutorBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand Down Expand Up @@ -52,6 +55,10 @@ public abstract class QueryExecutorBase implements QueryExecutor {
private final LruCache<Object, CachedQuery> statementCache;
private final CachedQueryCreateAction cachedQueryCreateAction;

// For getParameterStatuses(), GUC_REPORT tracking
private final TreeMap<String,String> parameterStatuses
= new TreeMap<String,String>(String.CASE_INSENSITIVE_ORDER);

protected QueryExecutorBase(PGStream pgStream, String user,
String database, int cancelSignalTimeout, Properties info) throws SQLException {
this.pgStream = pgStream;
Expand Down Expand Up @@ -389,4 +396,42 @@ public void setFlushCacheOnDeallocate(boolean flushCacheOnDeallocate) {
protected boolean hasNotifications() {
return notifications.size() > 0;
}

@Override
public final Map<String,String> getParameterStatuses() {
return Collections.unmodifiableMap(parameterStatuses);
}

@Override
public final String getParameterStatus(String parameterName) {
return parameterStatuses.get(parameterName);
}

/**
* Update the parameter status map in response to a new ParameterStatus
* wire protocol message.
*
* <p>The server sends ParameterStatus messages when GUC_REPORT settings are
* initially assigned and whenever they change.</p>
*
* <p>A future version may invoke a client-defined listener class at this point,
* so this should be the only access path.</p>
*
* <p>Keys are case-insensitive and case-preserving.</p>
*
* <p>The server doesn't provide a way to report deletion of a reportable
* parameter so we don't expose one here.</p>
*
* @param parameterName case-insensitive case-preserving name of parameter to create or update
* @param parameterStatus new value of parameter
* @see org.postgresql.PGConnection#getParameterStatuses
* @see org.postgresql.PGConnection#getParameterStatus
*/
protected void onParameterStatus(String parameterName, String parameterStatus) {
if (parameterName == null || parameterName.equals("")) {
throw new IllegalStateException("attempt to set GUC_REPORT parameter with null or empty-string name");
}

parameterStatuses.put(parameterName, parameterStatus);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2619,6 +2619,11 @@ public void receiveParameterStatus() throws IOException, SQLException {
LOGGER.log(Level.FINEST, " <=BE ParameterStatus({0} = {1})", new Object[]{name, value});
}

/* Update client-visible parameter status map for getParameterStatuses() */
if (name != null && !name.equals("")) {
onParameterStatus(name, value);
}

if (name.equals("client_encoding")) {
if (allowEncodingChanges) {
if (!value.equalsIgnoreCase("UTF8") && !value.equalsIgnoreCase("UTF-8")) {
Expand Down
11 changes: 11 additions & 0 deletions pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -1724,4 +1724,15 @@ public PreparedStatement prepareStatement(String sql, String[] columnNames) thro
}
return ps;
}

@Override
public final Map<String,String> getParameterStatuses() {
return queryExecutor.getParameterStatuses();
}

@Override
public final String getParameterStatus(String parameterName) {
return queryExecutor.getParameterStatus(parameterName);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@
CopyLargeFileTest.class,
ServerErrorTest.class,
UpsertTest.class,
OuterJoinSyntaxTest.class
OuterJoinSyntaxTest.class,
ParameterStatusTest.class
})
public class Jdbc2TestSuite {
}

0 comments on commit ce8333a

Please sign in to comment.