Skip to content

Commit 6aceb64

Browse files
authored
feat: add unknownLength connection property (#2286)
Adds an `unknownLength` connection property that can be used to configure the length that the JDBC driver should return as the data type / column length when this is not known. Spanner does not return the (maximum) length of a column in ResultSetMetadata. This means that the JDBC driver does not know what the length is of the various columns in query results. The value of `unknownLength` will be returned when the getPrecision and getColumnDisplaySize methods of ResultSetMetaData are called. This connection property aligns with the same connection parameter in the PostgreSQL JDBC driver: https://jdbc.postgresql.org/documentation/use/#connection-parameters
1 parent eacbecb commit 6aceb64

File tree

4 files changed

+35
-20
lines changed

4 files changed

+35
-20
lines changed

documentation/connection_properties.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ The 'Context' value indicates whether the property can only be set when a connec
1313
| autocommit_dml_mode | Determines the transaction type that is used to execute DML statements when the connection is in auto-commit mode. | TRANSACTIONAL | TRANSACTIONAL, PARTITIONED_NON_ATOMIC, TRANSACTIONAL_WITH_FALLBACK_TO_PARTITIONED_NON_ATOMIC, null | USER |
1414
| autoconfigemulator | Automatically configure the connection to try to connect to the Cloud Spanner emulator (true/false). The instance and database in the connection string will automatically be created if these do not yet exist on the emulator. Add dialect=postgresql to the connection string to make sure that the database that is created uses the PostgreSQL dialect. | false | true, false | STARTUP |
1515
| autopartitionmode | Execute all queries on this connection as partitioned queries. Executing a query that cannot be partitioned will fail. Executing a query in a read/write transaction will also fail. | false | true, false | USER |
16+
| batch_dml_update_count | The update count that is returned for DML statements that are executed in an explicit DML batch. The default is -1 | -1 | | USER |
1617
| channelprovider | The name of the channel provider class. The name must reference an implementation of ExternalChannelProvider. If this property is not set, the connection will use the default grpc channel provider. | | | STARTUP |
1718
| clientcertificate | Specifies the file path to the client certificate required for establishing an mTLS connection. | | | STARTUP |
1819
| clientkey | Specifies the file path to the client private key required for establishing an mTLS connection. | | | STARTUP |
1920
| connection_state_type | The type of connection state to use for this connection. Can only be set at start up. If no value is set, then the database dialect default will be used, which is NON_TRANSACTIONAL for GoogleSQL and TRANSACTIONAL for PostgreSQL. | | TRANSACTIONAL, NON_TRANSACTIONAL | STARTUP |
20-
| credentials | The location of the credentials file to use for this connection. If neither this property or encoded credentials are set, the connection will use the default Google Cloud credentials for the runtime environment. | | | STARTUP |
21+
| credentials | The location of the credentials file to use for this connection. If neither this property or encoded credentials are set, the connection will use the default Google Cloud credentials for the runtime environment. WARNING: Using this property without proper validation can expose the application to security risks. It is intended for use with credentials from a trusted source only, as it could otherwise allow end-users to supply arbitrary credentials. For more information, seehttps://cloud.google.com/docs/authentication/client-libraries#external-credentials | | | STARTUP |
2122
| credentialsprovider | The class name of the com.google.api.gax.core.CredentialsProvider implementation that should be used to obtain credentials for connections. | | | STARTUP |
2223
| databaserole | Sets the database role to use for this connection. The default is privileges assigned to IAM role | | | STARTUP |
2324
| databoostenabled | Enable data boost for all partitioned queries that are executed by this connection. This setting is only used for partitioned queries and is ignored by all other statements. | false | true, false | USER |
@@ -31,8 +32,9 @@ The 'Context' value indicates whether the property can only be set when a connec
3132
| enabledirectaccess | Configure the connection to try to connect to Spanner using DirectPath (true/false). The client will try to connect to Spanner using a direct Google network connection. DirectPath will work only if the client is trying to establish a connection from a Google Cloud VM. Otherwise it will automatically fallback to the standard network path. NOTE: The default for this property is currently false, but this could be changed in the future. | | true, false | STARTUP |
3233
| enableendtoendtracing | Enable end-to-end tracing (true/false) to generate traces for both the time that is spent in the client, as well as time that is spent in the Spanner server. Server side traces can only go to Google Cloud Trace, so to see end to end traces, the application should configure an exporter that exports the traces to Google Cloud Trace. | false | true, false | STARTUP |
3334
| enableextendedtracing | Include the SQL string in the OpenTelemetry traces that are generated by this connection. The SQL string is added as the standard OpenTelemetry attribute 'db.statement'. | | true, false | STARTUP |
34-
| encodedcredentials | Base64-encoded credentials to use for this connection. If neither this property or a credentials location are set, the connection will use the default Google Cloud credentials for the runtime environment. | | | STARTUP |
35+
| encodedcredentials | Base64-encoded credentials to use for this connection. If neither this property or a credentials location are set, the connection will use the default Google Cloud credentials for the runtime environment. WARNING: Enabling this property without proper validation can expose the application to security risks. It is intended for use with credentials from a trusted source only, as it could otherwise allow end-users to supply arbitrary credentials. For more information, seehttps://cloud.google.com/docs/authentication/client-libraries#external-credentials | | | STARTUP |
3536
| endpoint | The endpoint that the JDBC driver should connect to. The default is the default Spanner production endpoint when autoConfigEmulator=false, and the default Spanner emulator endpoint (localhost:9010) when autoConfigEmulator=true. This property takes precedence over any host name at the start of the connection URL. | | | STARTUP |
37+
| grpc_interceptor_provider | The class name of a com.google.api.gax.grpc.GrpcInterceptorProvider implementation that should be used to provide interceptors for the underlying Spanner client. This is a guarded property that can only be set if the Java System Property ENABLE_GRPC_INTERCEPTOR_PROVIDER has been set to true. This property should only be set to true on systems where an untrusted user cannot modify the connection URL, as using this property will dynamically invoke the constructor of the class specified. This means that any user that can modify the connection URL, can also dynamically invoke code on the host where the application is running. | | | STARTUP |
3638
| isexperimentalhost | Set this value to true for communication with a Experimental Host. | false | true, false | STARTUP |
3739
| keeptransactionalive | Enabling this option will trigger the connection to keep read/write transactions alive by executing a SELECT 1 query once every 10 seconds if no other statements are being executed. This option should be used with caution, as it can keep transactions alive and hold on to locks longer than intended. This option should typically be used for CLI-type application that might wait for user input for a longer period of time. | false | true, false | USER |
3840
| lenient | Silently ignore unknown properties in the connection string/properties (true/false) | false | true, false | STARTUP |
@@ -53,11 +55,13 @@ The 'Context' value indicates whether the property can only be set when a connec
5355
| routetoleader | Should read/write transactions and partitioned DML be routed to leader region (true/false) | true | true, false | STARTUP |
5456
| rpcpriority | Sets the priority for all RPC invocations from this connection (HIGH/MEDIUM/LOW). The default is HIGH. | | LOW, MEDIUM, HIGH, UNSPECIFIED, null | USER |
5557
| savepoint_support | Determines the behavior of the connection when savepoints are used. | FAIL_AFTER_ROLLBACK | ENABLED, FAIL_AFTER_ROLLBACK, DISABLED | USER |
58+
| statement_timeout | Adds a timeout to all statements executed on this connection. This property is only used when a statement timeout is specified. | | | USER |
5659
| tracing_prefix | The prefix that will be prepended to all OpenTelemetry traces that are generated by a Connection. | CloudSpanner | | STARTUP |
5760
| trackconnectionleaks | Capture the call stack of the thread that created a connection. This will pre-create a LeakedConnectionException already when a connection is created. This can be disabled, for example if a monitoring system logs the pre-created exception. If disabled, the LeakedConnectionException will only be created when an actual connection leak is detected. The stack trace of the exception will in that case not contain the call stack of when the connection was created. | true | true, false | STARTUP |
5861
| tracksessionleaks | Capture the call stack of the thread that checked out a session of the session pool. This will pre-create a LeakedSessionException already when a session is checked out. This can be disabled, for example if a monitoring system logs the pre-created exception. If disabled, the LeakedSessionException will only be created when an actual session leak is detected. The stack trace of the exception will in that case not contain the call stack of when the session was checked out. | true | true, false | STARTUP |
5962
| transaction_timeout | Timeout for read/write transactions. | | | USER |
6063
| universedomain | Configure the connection to try to connect to Spanner using a different partner Google Universe than GDU (googleapis.com). | googleapis.com | | STARTUP |
64+
| unknownlength | Spanner does not return the length of the selected columns in query results. When returning meta-data about these columns through functions like ResultSetMetaData.getColumnDisplaySize and ResultSetMetaData.getPrecision, we must provide a value. Various client tools and applications have different ideas about what they would like to see. This property specifies the length to return for types of unknown length. | 50 | | USER |
6165
| useautosavepointsforemulator | Automatically creates savepoints for each statement in a read/write transaction when using the Emulator. This is no longer needed when using Emulator version 1.5.23 or higher. | false | true, false | STARTUP |
6266
| useplaintext | Use a plain text communication channel (i.e. non-TLS) for communicating with the server (true/false). Set this value to true for communication with the Cloud Spanner emulator. | false | true, false | STARTUP |
6367
| useragent | The custom user-agent property name to use when communicating with Cloud Spanner. This property is intended for internal library usage, and should not be set by applications. | | | STARTUP |

src/main/java/com/google/cloud/spanner/jdbc/JdbcConnection.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.google.cloud.spanner.connection.AutocommitDmlMode;
3030
import com.google.cloud.spanner.connection.Connection;
3131
import com.google.cloud.spanner.connection.ConnectionOptions;
32+
import com.google.cloud.spanner.connection.ConnectionProperties;
3233
import com.google.cloud.spanner.connection.SavepointSupport;
3334
import com.google.cloud.spanner.connection.TransactionMode;
3435
import com.google.common.annotations.VisibleForTesting;
@@ -236,6 +237,11 @@ public String getOptimizerVersion() throws SQLException {
236237
return getSpannerConnection().getOptimizerVersion();
237238
}
238239

240+
/** Returns the value that should be returned for column types with an unknown length. */
241+
int getColumnTypeUnknownLength() {
242+
return getSpannerConnection().getConnectionPropertyValue(ConnectionProperties.UNKNOWN_LENGTH);
243+
}
244+
239245
@Override
240246
public boolean isInTransaction() throws SQLException {
241247
checkClosed();

src/main/java/com/google/cloud/spanner/jdbc/JdbcResultSetMetaData.java

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,16 @@
1717
package com.google.cloud.spanner.jdbc;
1818

1919
import com.google.cloud.spanner.ResultSet;
20+
import com.google.cloud.spanner.connection.ConnectionProperties;
2021
import com.google.common.base.Preconditions;
22+
import java.sql.Connection;
2123
import java.sql.ResultSetMetaData;
2224
import java.sql.SQLException;
2325
import java.sql.Statement;
2426
import java.sql.Types;
2527

2628
/** Implementation of {@link ResultSetMetaData} for Cloud Spanner */
2729
class JdbcResultSetMetaData extends AbstractJdbcWrapper implements ResultSetMetaData {
28-
/**
29-
* The default column display size for columns with a data type of variable size that is used when
30-
* the actual column size is not known.
31-
*/
32-
private static final int DEFAULT_COL_DISPLAY_SIZE_FOR_VARIABLE_LENGTH_COLS = 50;
33-
3430
private final ResultSet spannerResultSet;
3531
private final Statement statement;
3632

@@ -83,16 +79,15 @@ public boolean isSigned(int column) {
8379
}
8480

8581
@Override
86-
public int getColumnDisplaySize(int column) {
82+
public int getColumnDisplaySize(int column) throws SQLException {
8783
int colType = getColumnType(column);
8884
switch (colType) {
8985
case Types.ARRAY:
90-
return DEFAULT_COL_DISPLAY_SIZE_FOR_VARIABLE_LENGTH_COLS;
86+
return getUnknownLength();
9187
case Types.BOOLEAN:
9288
return 5;
9389
case Types.BINARY:
94-
int binaryLength = getPrecision(column);
95-
return binaryLength == 0 ? DEFAULT_COL_DISPLAY_SIZE_FOR_VARIABLE_LENGTH_COLS : binaryLength;
90+
return getPrecision(column);
9691
case Types.DATE:
9792
return 10;
9893
case Types.REAL:
@@ -105,8 +100,7 @@ public int getColumnDisplaySize(int column) {
105100
case Types.NUMERIC:
106101
return 14;
107102
case Types.NVARCHAR:
108-
int length = getPrecision(column);
109-
return length == 0 ? DEFAULT_COL_DISPLAY_SIZE_FOR_VARIABLE_LENGTH_COLS : length;
103+
return getPrecision(column);
110104
case Types.TIMESTAMP:
111105
return 16;
112106
default:
@@ -130,7 +124,7 @@ public String getSchemaName(int column) throws SQLException {
130124
}
131125

132126
@Override
133-
public int getPrecision(int column) {
127+
public int getPrecision(int column) throws SQLException {
134128
int colType = getColumnType(column);
135129
switch (colType) {
136130
case Types.BOOLEAN:
@@ -153,9 +147,20 @@ public int getPrecision(int column) {
153147
// For column types with variable size, such as text columns, we should return the length
154148
// in characters. We could try to fetch it from INFORMATION_SCHEMA, but that would mean
155149
// parsing the SQL statement client side in order to figure out which column it actually
156-
// is. For now we just return the default column display size.
157-
return DEFAULT_COL_DISPLAY_SIZE_FOR_VARIABLE_LENGTH_COLS;
150+
// is. Instead, we return a configurable fixed length. This is also consistent with for
151+
// example the PostgreSQL JDBC driver. See the 'unknownLength' connection property:
152+
// https://jdbc.postgresql.org/documentation/use/#connection-parameters
153+
return getUnknownLength();
154+
}
155+
}
156+
157+
private int getUnknownLength() throws SQLException {
158+
Connection connection = statement.getConnection();
159+
if (connection instanceof JdbcConnection) {
160+
JdbcConnection jdbcConnection = (JdbcConnection) connection;
161+
return jdbcConnection.getColumnTypeUnknownLength();
158162
}
163+
return ConnectionProperties.UNKNOWN_LENGTH.getDefaultValue();
159164
}
160165

161166
@Override

src/test/java/com/google/cloud/spanner/jdbc/JdbcResultSetMetaDataTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ public void testIsSigned() {
362362
}
363363

364364
@Test
365-
public void testGetColumnDisplaySize() {
365+
public void testGetColumnDisplaySize() throws SQLException {
366366
for (int i = 1; i <= TEST_COLUMNS.size(); i++) {
367367
assertEquals(
368368
"Wrong column display size for " + TEST_COLUMNS.get(i - 1).type,
@@ -371,7 +371,7 @@ public void testGetColumnDisplaySize() {
371371
}
372372
}
373373

374-
private int getDefaultDisplaySize(Type type, int column) {
374+
private int getDefaultDisplaySize(Type type, int column) throws SQLException {
375375
Preconditions.checkNotNull(type);
376376
switch (type.getCode()) {
377377
case BOOL:
@@ -425,7 +425,7 @@ public void testGetSchemaName() throws SQLException {
425425
}
426426

427427
@Test
428-
public void testGetPrecision() {
428+
public void testGetPrecision() throws SQLException {
429429
for (int i = 1; i <= TEST_COLUMNS.size(); i++) {
430430
assertEquals(
431431
"Wrong precision for type " + TEST_COLUMNS.get(i - 1).type,

0 commit comments

Comments
 (0)