diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java index 0ca9b7256e2..fd40efa8f4a 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperties.java @@ -112,20 +112,22 @@ import com.google.cloud.spanner.connection.ClientSideStatementValueConverters.StringValueConverter; import com.google.cloud.spanner.connection.ConnectionProperty.Context; import com.google.cloud.spanner.connection.DirectedReadOptionsUtil.DirectedReadOptionsConverter; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.spanner.v1.DirectedReadOptions; import java.time.Duration; -import java.util.Map; /** * Utility class that defines all known connection properties. This class will eventually replace * the list of {@link com.google.cloud.spanner.connection.ConnectionOptions.ConnectionProperty} in * {@link ConnectionOptions}. */ -class ConnectionProperties { +public class ConnectionProperties { private static final ImmutableMap.Builder> CONNECTION_PROPERTIES_BUILDER = ImmutableMap.builder(); + private static final Boolean[] BOOLEANS = new Boolean[] {Boolean.TRUE, Boolean.FALSE}; + static final ConnectionProperty CONNECTION_STATE_TYPE = create( "connection_state_type", @@ -133,6 +135,7 @@ class ConnectionProperties { + "If no value is set, then the database dialect default will be used, " + "which is NON_TRANSACTIONAL for GoogleSQL and TRANSACTIONAL for PostgreSQL.", null, + ConnectionState.Type.values(), ConnectionStateTypeConverter.INSTANCE, Context.STARTUP); static final ConnectionProperty TRACING_PREFIX = @@ -148,6 +151,7 @@ class ConnectionProperties { LENIENT_PROPERTY_NAME, "Silently ignore unknown properties in the connection string/properties (true/false)", DEFAULT_LENIENT, + BOOLEANS, BooleanConverter.INSTANCE, Context.STARTUP); static final ConnectionProperty ENDPOINT = @@ -167,6 +171,7 @@ class ConnectionProperties { + "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, + BOOLEANS, BooleanConverter.INSTANCE, Context.STARTUP); static final ConnectionProperty USE_AUTO_SAVEPOINTS_FOR_EMULATOR = @@ -175,6 +180,7 @@ class ConnectionProperties { "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, + BOOLEANS, BooleanConverter.INSTANCE, Context.STARTUP); static final ConnectionProperty USE_PLAIN_TEXT = @@ -182,6 +188,7 @@ class ConnectionProperties { USE_PLAIN_TEXT_PROPERTY_NAME, "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.", DEFAULT_USE_PLAIN_TEXT, + BOOLEANS, BooleanConverter.INSTANCE, Context.STARTUP); @@ -226,6 +233,7 @@ class ConnectionProperties { DIALECT_PROPERTY_NAME, "Sets the dialect to use for new databases that are created by this connection.", Dialect.GOOGLE_STANDARD_SQL, + Dialect.values(), DialectConverter.INSTANCE, Context.STARTUP); static final ConnectionProperty TRACK_SESSION_LEAKS = @@ -238,6 +246,7 @@ class ConnectionProperties { + "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.", DEFAULT_TRACK_SESSION_LEAKS, + BOOLEANS, BooleanConverter.INSTANCE, Context.STARTUP); static final ConnectionProperty TRACK_CONNECTION_LEAKS = @@ -250,6 +259,7 @@ class ConnectionProperties { + "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.", DEFAULT_TRACK_CONNECTION_LEAKS, + BOOLEANS, BooleanConverter.INSTANCE, Context.STARTUP); static final ConnectionProperty ROUTE_TO_LEADER = @@ -257,6 +267,7 @@ class ConnectionProperties { ROUTE_TO_LEADER_PROPERTY_NAME, "Should read/write transactions and partitioned DML be routed to leader region (true/false)", DEFAULT_ROUTE_TO_LEADER, + BOOLEANS, BooleanConverter.INSTANCE, Context.STARTUP); static final ConnectionProperty USE_VIRTUAL_THREADS = @@ -265,6 +276,7 @@ class ConnectionProperties { "Use a virtual thread instead of a platform thread for each connection (true/false). " + "This option only has any effect if the application is running on Java 21 or higher. In all other cases, the option is ignored.", DEFAULT_USE_VIRTUAL_THREADS, + BOOLEANS, BooleanConverter.INSTANCE, Context.STARTUP); static final ConnectionProperty USE_VIRTUAL_GRPC_TRANSPORT_THREADS = @@ -273,6 +285,7 @@ class ConnectionProperties { "Use a virtual thread instead of a platform thread for the gRPC executor (true/false). " + "This option only has any effect if the application is running on Java 21 or higher. In all other cases, the option is ignored.", DEFAULT_USE_VIRTUAL_GRPC_TRANSPORT_THREADS, + BOOLEANS, BooleanConverter.INSTANCE, Context.STARTUP); static final ConnectionProperty ENABLE_EXTENDED_TRACING = @@ -282,6 +295,7 @@ class ConnectionProperties { + "by this connection. The SQL string is added as the standard OpenTelemetry " + "attribute 'db.statement'.", DEFAULT_ENABLE_EXTENDED_TRACING, + BOOLEANS, BooleanConverter.INSTANCE, Context.STARTUP); static final ConnectionProperty ENABLE_API_TRACING = @@ -292,6 +306,7 @@ class ConnectionProperties { + "or if you want to debug potential latency problems caused by RPCs that are " + "being retried.", DEFAULT_ENABLE_API_TRACING, + BOOLEANS, BooleanConverter.INSTANCE, Context.STARTUP); static final ConnectionProperty ENABLE_END_TO_END_TRACING = @@ -302,6 +317,7 @@ class ConnectionProperties { + "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.", DEFAULT_ENABLE_END_TO_END_TRACING, + BOOLEANS, BooleanConverter.INSTANCE, Context.STARTUP); static final ConnectionProperty MIN_SESSIONS = @@ -345,6 +361,7 @@ class ConnectionProperties { AUTOCOMMIT_PROPERTY_NAME, "Should the connection start in autocommit (true/false)", DEFAULT_AUTOCOMMIT, + BOOLEANS, BooleanConverter.INSTANCE, Context.USER); static final ConnectionProperty READONLY = @@ -352,13 +369,16 @@ class ConnectionProperties { READONLY_PROPERTY_NAME, "Should the connection start in read-only mode (true/false)", DEFAULT_READONLY, + BOOLEANS, BooleanConverter.INSTANCE, Context.USER); static final ConnectionProperty AUTOCOMMIT_DML_MODE = create( "autocommit_dml_mode", - "Should the connection automatically retry Aborted errors (true/false)", + "Determines the transaction type that is used to execute " + + "DML statements when the connection is in auto-commit mode.", AutocommitDmlMode.TRANSACTIONAL, + AutocommitDmlMode.values(), AutocommitDmlModeConverter.INSTANCE, Context.USER); static final ConnectionProperty RETRY_ABORTS_INTERNALLY = @@ -371,6 +391,7 @@ class ConnectionProperties { RETRY_ABORTS_INTERNALLY_PROPERTY_NAME, "Should the connection automatically retry Aborted errors (true/false)", DEFAULT_RETRY_ABORTS_INTERNALLY, + BOOLEANS, BooleanConverter.INSTANCE, Context.USER); static final ConnectionProperty RETURN_COMMIT_STATS = @@ -378,6 +399,7 @@ class ConnectionProperties { "returnCommitStats", "Request that Spanner returns commit statistics for read/write transactions (true/false)", DEFAULT_RETURN_COMMIT_STATS, + BOOLEANS, BooleanConverter.INSTANCE, Context.USER); static final ConnectionProperty DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE = @@ -389,6 +411,7 @@ class ConnectionProperties { + "the first write operation in a read/write transaction will be executed using the read/write transaction. Enabling this mode can reduce locking " + "and improve performance for applications that can handle the lower transaction isolation semantics.", DEFAULT_DELAY_TRANSACTION_START_UNTIL_FIRST_WRITE, + BOOLEANS, BooleanConverter.INSTANCE, Context.USER); static final ConnectionProperty KEEP_TRANSACTION_ALIVE = @@ -398,6 +421,7 @@ class ConnectionProperties { + "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.", DEFAULT_KEEP_TRANSACTION_ALIVE, + BOOLEANS, BooleanConverter.INSTANCE, Context.USER); @@ -415,6 +439,7 @@ class ConnectionProperties { + "Executing a query that cannot be partitioned will fail. " + "Executing a query in a read/write transaction will also fail.", DEFAULT_AUTO_PARTITION_MODE, + BOOLEANS, BooleanConverter.INSTANCE, Context.USER); static final ConnectionProperty DATA_BOOST_ENABLED = @@ -423,6 +448,7 @@ class ConnectionProperties { "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.", DEFAULT_DATA_BOOST_ENABLED, + BOOLEANS, BooleanConverter.INSTANCE, Context.USER); static final ConnectionProperty MAX_PARTITIONS = @@ -468,6 +494,7 @@ class ConnectionProperties { RPC_PRIORITY_NAME, "Sets the priority for all RPC invocations from this connection (HIGH/MEDIUM/LOW). The default is HIGH.", DEFAULT_RPC_PRIORITY, + RpcPriority.values(), RpcPriorityConverter.INSTANCE, Context.USER); static final ConnectionProperty SAVEPOINT_SUPPORT = @@ -475,6 +502,7 @@ class ConnectionProperties { "savepoint_support", "Determines the behavior of the connection when savepoints are used.", SavepointSupport.FAIL_AFTER_ROLLBACK, + SavepointSupport.values(), SavepointSupportConverter.INSTANCE, Context.USER); static final ConnectionProperty DDL_IN_TRANSACTION_MODE = @@ -482,6 +510,7 @@ class ConnectionProperties { DDL_IN_TRANSACTION_MODE_PROPERTY_NAME, "Determines how the connection should handle DDL statements in a read/write transaction.", DEFAULT_DDL_IN_TRANSACTION_MODE, + DdlInTransactionMode.values(), DdlInTransactionModeConverter.INSTANCE, Context.USER); static final ConnectionProperty MAX_COMMIT_DELAY = @@ -504,6 +533,7 @@ class ConnectionProperties { + "This setting is only in read/write transactions. DML statements in auto-commit mode " + "are executed directly.", DEFAULT_AUTO_BATCH_DML, + BOOLEANS, BooleanConverter.INSTANCE, Context.USER); static final ConnectionProperty AUTO_BATCH_DML_UPDATE_COUNT = @@ -538,12 +568,17 @@ class ConnectionProperties { + AUTO_BATCH_DML_UPDATE_COUNT_VERIFICATION_PROPERTY_NAME + " to false.", DEFAULT_AUTO_BATCH_DML_UPDATE_COUNT_VERIFICATION, + BOOLEANS, BooleanConverter.INSTANCE, Context.USER); - static final Map> CONNECTION_PROPERTIES = + static final ImmutableMap> CONNECTION_PROPERTIES = CONNECTION_PROPERTIES_BUILDER.build(); + /** The list of all supported connection properties. */ + public static ImmutableList> VALID_CONNECTION_PROPERTIES = + ImmutableList.copyOf(ConnectionProperties.CONNECTION_PROPERTIES.values()); + /** Utility method for creating a new core {@link ConnectionProperty}. */ private static ConnectionProperty create( String name, @@ -551,10 +586,27 @@ private static ConnectionProperty create( T defaultValue, ClientSideStatementValueConverter converter, Context context) { - ConnectionProperty property = - ConnectionProperty.create(name, description, defaultValue, converter, context); - CONNECTION_PROPERTIES_BUILDER.put(property.getKey(), property); - return property; + return create(name, description, defaultValue, null, converter, context); + } + + /** Utility method for creating a new core {@link ConnectionProperty}. */ + private static ConnectionProperty create( + String name, + String description, + T defaultValue, + T[] validValues, + ClientSideStatementValueConverter converter, + Context context) { + try { + ConnectionProperty property = + ConnectionProperty.create( + name, description, defaultValue, validValues, converter, context); + CONNECTION_PROPERTIES_BUILDER.put(property.getKey(), property); + return property; + } catch (Throwable t) { + t.printStackTrace(); + } + return null; } /** Parse the connection properties that can be found in the given connection URL. */ diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperty.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperty.java index c203d44203b..7c06774cf2f 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperty.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionProperty.java @@ -37,12 +37,13 @@ * connection state is an opt-in. */ public class ConnectionProperty { + /** * Context indicates when a {@link ConnectionProperty} may be set. Each higher-ordinal value * includes the preceding values, meaning that a {@link ConnectionProperty} with {@link * Context#USER} can be set both at connection startup and during the connection's lifetime. */ - enum Context { + public enum Context { /** The property can only be set at startup of the connection. */ STARTUP, /** @@ -79,8 +80,20 @@ static ConnectionProperty create( T defaultValue, ClientSideStatementValueConverter converter, Context context) { + return create(name, description, defaultValue, null, converter, context); + } + + /** Utility method for creating a typed {@link ConnectionProperty}. */ + @Nonnull + static ConnectionProperty create( + @Nonnull String name, + String description, + T defaultValue, + T[] validValues, + ClientSideStatementValueConverter converter, + Context context) { return new ConnectionProperty<>( - null, name, description, defaultValue, null, converter, context); + null, name, description, defaultValue, validValues, converter, context); } /** @@ -163,35 +176,38 @@ ConnectionPropertyValue convert(@Nullable String stringValue) { return new ConnectionPropertyValue<>(this, convertedValue, convertedValue); } - String getKey() { + @Nonnull + public String getKey() { return this.key; } - boolean hasExtension() { + public boolean hasExtension() { return this.extension != null; } - String getExtension() { + public String getExtension() { return this.extension; } - String getName() { + @Nonnull + public String getName() { return this.name; } - String getDescription() { + @Nonnull + public String getDescription() { return this.description; } - T getDefaultValue() { + public T getDefaultValue() { return this.defaultValue; } - T[] getValidValues() { + public T[] getValidValues() { return this.validValues; } - Context getContext() { + public Context getContext() { return this.context; } }