From ef5b0e2bbca1ffda136964671fd11e9a4005d8f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Fri, 13 Dec 2024 13:56:42 +0100 Subject: [PATCH] chore: make valid connection properties public Make the list of valid connection properties public, so tools that depend on the Connection API can use this to for example generate documentation for valid properties. Also add valid values to the connection properties that have that (e.g. enums and booleans). --- .../connection/ConnectionProperties.java | 68 ++++++++++++++++--- .../connection/ConnectionProperty.java | 36 +++++++--- 2 files changed, 86 insertions(+), 18 deletions(-) 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; } }