From e2945324783bc6d5a7a323578e8dbf00969f3163 Mon Sep 17 00:00:00 2001 From: Thiago Nunes Date: Sat, 5 Jun 2021 19:48:23 +1000 Subject: [PATCH] feat: adds query optimizer statistics support (#385) * feat: adds optimizer_statistics_package option Adds the possibility to set the optimizer statistics package when executing queries. This option can be set in different levels as follows: 1. Through statement hints 2. Through query level configuration 3. Through an environment variable 4. Through application level configuration If more than one package is set (in different levels) the precedence is from 1 to 4 (1 has top priority). * test: adds integration tests Adds integration tests for setting the query options optimizer statistics package. * fix: addresses PR comments Fix missing value in the documentation of the optimizer statistics package for the connection class. * fix: adds tests for invalid stats packages Adds tests for invalid statistics packages (whitespace only ones). * fix: adds integration tests for query options Adds an integration test to run the sql script with several expectations for the query options. These are tests for the optimizer version and optimizer statistics package. * fix: formatting of ClientSideStatements.json This file is using a mix of tabs and spaces. For now I have formatted as the other lines, so that the diff is clear. In a further PR I will reformat the whole file to use only spaces. * tests: fix Connection test Fixes connection test when using environment variables for retrieving configurations. This only works when a first connection is created, so we moved this specific test to its own subclass. * fix: fix clirr checks Provides default interface implementations and fixes clirr checks --- .../clirr-ignored-differences.xml | 19 +++ .../google/cloud/spanner/SpannerOptions.java | 18 +++ .../cloud/spanner/connection/Connection.java | 28 +++++ .../spanner/connection/ConnectionImpl.java | 14 +++ .../spanner/connection/ConnectionOptions.java | 13 +++ .../ConnectionStatementExecutor.java | 4 + .../ConnectionStatementExecutorImpl.java | 16 +++ .../spanner/connection/StatementResult.java | 2 + .../connection/ClientSideStatements.json | 24 +++- .../spanner/AbstractReadContextTest.java | 15 ++- .../cloud/spanner/DatabaseClientImplTest.java | 33 +++++- .../google/cloud/spanner/SpannerImplTest.java | 6 +- .../cloud/spanner/SpannerOptionsTest.java | 63 ++++++++-- .../AbstractConnectionImplTest.java | 45 +++++++ .../connection/ConnectionImplTest.java | 110 ++++++++++++++++-- .../ConnectionStatementExecutorTest.java | 14 +++ ...nnectionStatementWithNoParametersTest.java | 15 +++ ...nnectionStatementWithOneParameterTest.java | 16 +++ .../spanner/connection/ConnectionTest.java | 69 ++++++++--- .../connection/it/ITQueryOptionsTest.java | 43 +++++++ .../cloud/spanner/it/ITQueryOptionsTest.java | 28 ++++- .../ITSqlScriptTest_TestQueryOptions.sql | 23 +++- 22 files changed, 568 insertions(+), 50 deletions(-) create mode 100644 google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITQueryOptionsTest.java diff --git a/google-cloud-spanner/clirr-ignored-differences.xml b/google-cloud-spanner/clirr-ignored-differences.xml index c6a936c51c..75f83555c9 100644 --- a/google-cloud-spanner/clirr-ignored-differences.xml +++ b/google-cloud-spanner/clirr-ignored-differences.xml @@ -618,4 +618,23 @@ com/google/cloud/spanner/TransactionContext com.google.api.core.ApiFuture bufferAsync(java.lang.Iterable) + + + + + 7012 + com/google/cloud/spanner/SpannerOptions$SpannerEnvironment + java.lang.String getOptimizerStatisticsPackage() + + + 7012 + com/google/cloud/spanner/connection/Connection + java.lang.String getOptimizerStatisticsPackage() + + + 7012 + com/google/cloud/spanner/connection/Connection + void setOptimizerStatisticsPackage(java.lang.String) + + diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java index 88587c6428..3274048c61 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java @@ -585,6 +585,15 @@ public interface SpannerEnvironment { */ @Nonnull String getOptimizerVersion(); + + /** + * The optimizer statistics package to use. Must return an empty string to indicate that no + * value has been set. + */ + @Nonnull + default String getOptimizerStatisticsPackage() { + throw new UnsupportedOperationException("Unimplemented"); + } } /** @@ -594,6 +603,8 @@ public interface SpannerEnvironment { private static class SpannerEnvironmentImpl implements SpannerEnvironment { private static final SpannerEnvironmentImpl INSTANCE = new SpannerEnvironmentImpl(); private static final String SPANNER_OPTIMIZER_VERSION_ENV_VAR = "SPANNER_OPTIMIZER_VERSION"; + private static final String SPANNER_OPTIMIZER_STATISTICS_PACKAGE_ENV_VAR = + "SPANNER_OPTIMIZER_STATISTICS_PACKAGE"; private SpannerEnvironmentImpl() {} @@ -601,6 +612,12 @@ private SpannerEnvironmentImpl() {} public String getOptimizerVersion() { return MoreObjects.firstNonNull(System.getenv(SPANNER_OPTIMIZER_VERSION_ENV_VAR), ""); } + + @Override + public String getOptimizerStatisticsPackage() { + return MoreObjects.firstNonNull( + System.getenv(SPANNER_OPTIMIZER_STATISTICS_PACKAGE_ENV_VAR), ""); + } } /** Builder for {@link SpannerOptions} instances. */ @@ -957,6 +974,7 @@ public Builder setDefaultQueryOptions(DatabaseId database, QueryOptions defaultQ QueryOptions getEnvironmentQueryOptions() { return QueryOptions.newBuilder() .setOptimizerVersion(environment.getOptimizerVersion()) + .setOptimizerStatisticsPackage(environment.getOptimizerStatisticsPackage()) .build(); } diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java index 813dda25cc..fb7e7bca01 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java @@ -96,6 +96,11 @@ *
  • * SET OPTIMIZER_VERSION='<version>' | 'LATEST' * : Sets the value of OPTIMIZER_VERSION for this connection. + *
  • SHOW OPTIMIZER_STATISTICS_PACKAGE: Returns the current value of + * OPTIMIZER_STATISTICS_PACKAGE of this connection as a {@link ResultSet} + *
  • + * SET OPTIMIZER_STATISTICS_PACKAGE='<package>' | '' + * : Sets the value of OPTIMIZER_STATISTICS_PACKAGE for this connection. *
  • BEGIN [TRANSACTION]: Begins a new transaction. This statement is optional when * the connection is not in autocommit mode, as a new transaction will automatically be * started when a query or update statement is issued. In autocommit mode, this statement will @@ -448,6 +453,29 @@ public interface Connection extends AutoCloseable { */ String getOptimizerVersion(); + /** + * Sets the query optimizer statistics package + * + * @param optimizerStatisticsPackage The query optimizer statistics package to use. Must be a + * string composed of letters, numbers, dashes and underscores or an empty string. The empty + * string will instruct the connection to use the optimizer statistics package that is defined + * the environment variable SPANNER_OPTIMIZER_STATISTICS_PACKAGE. If no value is + * specified in the environment variable, the client level query optimizer is used. If none is + * set, the default query optimizer of Cloud Spanner is used. + */ + default void setOptimizerStatisticsPackage(String optimizerStatisticsPackage) { + throw new UnsupportedOperationException("Unimplemented"); + } + + /** + * Gets the current query optimizer statistics package of this connection. + * + * @return The query optimizer statistics package that is currently used by this connection. + */ + default String getOptimizerStatisticsPackage() { + throw new UnsupportedOperationException("Unimplemented"); + } + /** * Sets whether this connection should request commit statistics from Cloud Spanner for read/write * transactions and DML statements in autocommit mode. diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java index 345c881d29..ecf79a8086 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java @@ -433,6 +433,20 @@ public String getOptimizerVersion() { return this.queryOptions.getOptimizerVersion(); } + @Override + public void setOptimizerStatisticsPackage(String optimizerStatisticsPackage) { + Preconditions.checkNotNull(optimizerStatisticsPackage); + ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG); + this.queryOptions = + queryOptions.toBuilder().setOptimizerStatisticsPackage(optimizerStatisticsPackage).build(); + } + + @Override + public String getOptimizerStatisticsPackage() { + ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG); + return this.queryOptions.getOptimizerStatisticsPackage(); + } + @Override public void setStatementTimeout(long timeout, TimeUnit unit) { Preconditions.checkArgument(timeout > 0L, "Zero or negative timeout values are not allowed"); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java index f5ccaddf6b..3a1f321d09 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java @@ -157,6 +157,7 @@ public String[] getValidValues() { private static final String DEFAULT_NUM_CHANNELS = null; private static final String DEFAULT_USER_AGENT = null; private static final String DEFAULT_OPTIMIZER_VERSION = ""; + private static final String DEFAULT_OPTIMIZER_STATISTICS_PACKAGE = ""; private static final boolean DEFAULT_RETURN_COMMIT_STATS = false; private static final boolean DEFAULT_LENIENT = false; @@ -190,6 +191,9 @@ public String[] getValidValues() { private static final String USER_AGENT_PROPERTY_NAME = "userAgent"; /** Query optimizer version to use for a connection. */ private static final String OPTIMIZER_VERSION_PROPERTY_NAME = "optimizerVersion"; + /** Query optimizer statistics package to use for a connection. */ + private static final String OPTIMIZER_STATISTICS_PACKAGE_PROPERTY_NAME = + "optimizerStatisticsPackage"; /** Name of the 'lenientMode' connection property. */ public static final String LENIENT_PROPERTY_NAME = "lenient"; @@ -238,6 +242,8 @@ public String[] getValidValues() { ConnectionProperty.createStringProperty( OPTIMIZER_VERSION_PROPERTY_NAME, "Sets the default query optimizer version to use for this connection."), + ConnectionProperty.createStringProperty( + OPTIMIZER_STATISTICS_PACKAGE_PROPERTY_NAME, ""), ConnectionProperty.createBooleanProperty("returnCommitStats", "", false), ConnectionProperty.createBooleanProperty( "autoConfigEmulator", @@ -521,6 +527,7 @@ private ConnectionOptions(Builder builder) { this.userAgent = parseUserAgent(this.uri); QueryOptions.Builder queryOptionsBuilder = QueryOptions.newBuilder(); queryOptionsBuilder.setOptimizerVersion(parseOptimizerVersion(this.uri)); + queryOptionsBuilder.setOptimizerStatisticsPackage(parseOptimizerStatisticsPackage(this.uri)); this.queryOptions = queryOptionsBuilder.build(); this.returnCommitStats = parseReturnCommitStats(this.uri); this.autoConfigEmulator = parseAutoConfigEmulator(this.uri); @@ -695,6 +702,12 @@ static String parseOptimizerVersion(String uri) { return value != null ? value : DEFAULT_OPTIMIZER_VERSION; } + @VisibleForTesting + static String parseOptimizerStatisticsPackage(String uri) { + String value = parseUriProperty(uri, OPTIMIZER_STATISTICS_PACKAGE_PROPERTY_NAME); + return value != null ? value : DEFAULT_OPTIMIZER_STATISTICS_PACKAGE; + } + @VisibleForTesting static boolean parseReturnCommitStats(String uri) { String value = parseUriProperty(uri, "returnCommitStats"); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutor.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutor.java index 5cbbdfb7c4..3f6dba9a40 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutor.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutor.java @@ -66,6 +66,10 @@ interface ConnectionStatementExecutor { StatementResult statementShowOptimizerVersion(); + StatementResult statementSetOptimizerStatisticsPackage(String optimizerStatisticsPackage); + + StatementResult statementShowOptimizerStatisticsPackage(); + StatementResult statementSetReturnCommitStats(Boolean returnCommitStats); StatementResult statementShowReturnCommitStats(); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorImpl.java index 1d7a202c5e..0f3f7470a6 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorImpl.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorImpl.java @@ -23,6 +23,7 @@ import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.RUN_BATCH; import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_AUTOCOMMIT; import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_AUTOCOMMIT_DML_MODE; +import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_OPTIMIZER_STATISTICS_PACKAGE; import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_OPTIMIZER_VERSION; import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_READONLY; import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SET_READ_ONLY_STALENESS; @@ -34,6 +35,7 @@ import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_AUTOCOMMIT_DML_MODE; import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_COMMIT_RESPONSE; import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_COMMIT_TIMESTAMP; +import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_OPTIMIZER_STATISTICS_PACKAGE; import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_OPTIMIZER_VERSION; import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_READONLY; import static com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType.SHOW_READ_ONLY_STALENESS; @@ -230,6 +232,20 @@ public StatementResult statementShowOptimizerVersion() { "OPTIMIZER_VERSION", getConnection().getOptimizerVersion(), SHOW_OPTIMIZER_VERSION); } + @Override + public StatementResult statementSetOptimizerStatisticsPackage(String optimizerStatisticsPackage) { + getConnection().setOptimizerStatisticsPackage(optimizerStatisticsPackage); + return noResult(SET_OPTIMIZER_STATISTICS_PACKAGE); + } + + @Override + public StatementResult statementShowOptimizerStatisticsPackage() { + return resultSet( + "OPTIMIZER_STATISTICS_PACKAGE", + getConnection().getOptimizerStatisticsPackage(), + SHOW_OPTIMIZER_STATISTICS_PACKAGE); + } + @Override public StatementResult statementSetReturnCommitStats(Boolean returnCommitStats) { getConnection().setReturnCommitStats(returnCommitStats); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementResult.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementResult.java index 94269d6ac7..5ece3a20e9 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementResult.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/StatementResult.java @@ -65,6 +65,8 @@ enum ClientSideStatementType { SET_READ_ONLY_STALENESS, SHOW_OPTIMIZER_VERSION, SET_OPTIMIZER_VERSION, + SHOW_OPTIMIZER_STATISTICS_PACKAGE, + SET_OPTIMIZER_STATISTICS_PACKAGE, SHOW_RETURN_COMMIT_STATS, SET_RETURN_COMMIT_STATS, BEGIN, diff --git a/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/ClientSideStatements.json b/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/ClientSideStatements.json index 6761374ea5..4dfe480395 100644 --- a/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/ClientSideStatements.json +++ b/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/ClientSideStatements.json @@ -76,6 +76,14 @@ "method": "statementShowOptimizerVersion", "exampleStatements": ["show variable optimizer_version"] }, + { + "name": "SHOW VARIABLE OPTIMIZER_STATISTICS_PACKAGE", + "executorName": "ClientSideStatementNoParamExecutor", + "resultType": "RESULT_SET", + "regex": "(?is)\\A\\s*show\\s+variable\\s+optimizer_statistics_package\\s*\\z", + "method": "statementShowOptimizerStatisticsPackage", + "exampleStatements": ["show variable optimizer_statistics_package"] + }, { "name": "SHOW VARIABLE RETURN_COMMIT_STATS", "executorName": "ClientSideStatementNoParamExecutor", @@ -281,6 +289,20 @@ "converterName": "ClientSideStatementValueConverters$StringValueConverter" } }, + { + "name": "SET OPTIMIZER_STATISTICS_PACKAGE = ''|''", + "executorName": "ClientSideStatementSetExecutor", + "resultType": "NO_RESULT", + "regex": "(?is)\\A\\s*set\\s+optimizer_statistics_package\\s*(?:=)\\s*(.*)\\z", + "method": "statementSetOptimizerStatisticsPackage", + "exampleStatements": ["set optimizer_statistics_package='auto_20191128_14_47_22UTC'", "set optimizer_statistics_package=''"], + "setStatement": { + "propertyName": "OPTIMIZER_STATISTICS_PACKAGE", + "separator": "=", + "allowedValues": "'((\\S+)|())'", + "converterName": "ClientSideStatementValueConverters$StringValueConverter" + } + }, { "name": "SET RETURN_COMMIT_STATS = TRUE|FALSE", "executorName": "ClientSideStatementSetExecutor", @@ -296,4 +318,4 @@ } } ] -} \ No newline at end of file +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractReadContextTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractReadContextTest.java index 16f5dc8a25..66c3609d6f 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractReadContextTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/AbstractReadContextTest.java @@ -52,7 +52,12 @@ public static Collection parameters() { List params = new ArrayList<>(); params.add(new Object[] {QueryOptions.getDefaultInstance()}); params.add( - new Object[] {QueryOptions.newBuilder().setOptimizerVersion("some-version").build()}); + new Object[] { + QueryOptions.newBuilder() + .setOptimizerVersion("some-version") + .setOptimizerStatisticsPackage("some-package") + .build() + }); return params; } @@ -134,7 +139,11 @@ public void executeSqlRequestBuilderWithQueryOptions() { context .getExecuteSqlRequestBuilder( Statement.newBuilder("SELECT FOO FROM BAR") - .withQueryOptions(QueryOptions.newBuilder().setOptimizerVersion("2.0").build()) + .withQueryOptions( + QueryOptions.newBuilder() + .setOptimizerVersion("2.0") + .setOptimizerStatisticsPackage("custom-package") + .build()) .build(), QueryMode.NORMAL, Options.fromQueryOptions(), @@ -142,6 +151,8 @@ public void executeSqlRequestBuilderWithQueryOptions() { .build(); assertThat(request.getSql()).isEqualTo("SELECT FOO FROM BAR"); assertThat(request.getQueryOptions().getOptimizerVersion()).isEqualTo("2.0"); + assertThat(request.getQueryOptions().getOptimizerStatisticsPackage()) + .isEqualTo("custom-package"); } @Test diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java index 42c7bb1b94..0a1efc2a48 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/DatabaseClientImplTest.java @@ -1529,18 +1529,25 @@ public void testBackendQueryOptions() { .singleUse() .executeQuery( Statement.newBuilder(SELECT1.getSql()) - .withQueryOptions(QueryOptions.newBuilder().setOptimizerVersion("1").build()) + .withQueryOptions( + QueryOptions.newBuilder() + .setOptimizerVersion("1") + .setOptimizerStatisticsPackage("custom-package") + .build()) .build())) { // Just iterate over the results to execute the query. while (rs.next()) {} } - // Check that the last query was executed using a custom optimizer version. + // Check that the last query was executed using a custom optimizer version and statistics + // package. List requests = mockSpanner.getRequests(); assertThat(requests).isNotEmpty(); assertThat(requests.get(requests.size() - 1)).isInstanceOf(ExecuteSqlRequest.class); ExecuteSqlRequest request = (ExecuteSqlRequest) requests.get(requests.size() - 1); assertThat(request.getQueryOptions()).isNotNull(); assertThat(request.getQueryOptions().getOptimizerVersion()).isEqualTo("1"); + assertThat(request.getQueryOptions().getOptimizerStatisticsPackage()) + .isEqualTo("custom-package"); } } @@ -1562,20 +1569,27 @@ public void testBackendQueryOptionsWithAnalyzeQuery() { try (ResultSet rs = tx.analyzeQuery( Statement.newBuilder(SELECT1.getSql()) - .withQueryOptions(QueryOptions.newBuilder().setOptimizerVersion("1").build()) + .withQueryOptions( + QueryOptions.newBuilder() + .setOptimizerVersion("1") + .setOptimizerStatisticsPackage("custom-package") + .build()) .build(), QueryAnalyzeMode.PROFILE)) { // Just iterate over the results to execute the query. while (rs.next()) {} } } - // Check that the last query was executed using a custom optimizer version. + // Check that the last query was executed using a custom optimizer version and statistics + // package. List requests = mockSpanner.getRequests(); assertThat(requests).isNotEmpty(); assertThat(requests.get(requests.size() - 1)).isInstanceOf(ExecuteSqlRequest.class); ExecuteSqlRequest request = (ExecuteSqlRequest) requests.get(requests.size() - 1); assertThat(request.getQueryOptions()).isNotNull(); assertThat(request.getQueryOptions().getOptimizerVersion()).isEqualTo("1"); + assertThat(request.getQueryOptions().getOptimizerStatisticsPackage()) + .isEqualTo("custom-package"); assertThat(request.getQueryMode()).isEqualTo(QueryMode.PROFILE); } } @@ -1600,19 +1614,26 @@ public void testBackendPartitionQueryOptions() { transaction.partitionQuery( PartitionOptions.newBuilder().setMaxPartitions(10L).build(), Statement.newBuilder(SELECT1.getSql()) - .withQueryOptions(QueryOptions.newBuilder().setOptimizerVersion("1").build()) + .withQueryOptions( + QueryOptions.newBuilder() + .setOptimizerVersion("1") + .setOptimizerStatisticsPackage("custom-package") + .build()) .build()); try (ResultSet rs = transaction.execute(partitions.get(0))) { // Just iterate over the results to execute the query. while (rs.next()) {} } - // Check that the last query was executed using a custom optimizer version. + // Check that the last query was executed using a custom optimizer version and statistics + // package. List requests = mockSpanner.getRequests(); assertThat(requests).isNotEmpty(); assertThat(requests.get(requests.size() - 1)).isInstanceOf(ExecuteSqlRequest.class); ExecuteSqlRequest request = (ExecuteSqlRequest) requests.get(requests.size() - 1); assertThat(request.getQueryOptions()).isNotNull(); assertThat(request.getQueryOptions().getOptimizerVersion()).isEqualTo("1"); + assertThat(request.getQueryOptions().getOptimizerStatisticsPackage()) + .isEqualTo("custom-package"); } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerImplTest.java index c8a93d64ee..0cc0e6e9fb 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerImplTest.java @@ -96,7 +96,11 @@ public void getDbclientAgainGivesSame() { @Test public void queryOptions() { - QueryOptions queryOptions = QueryOptions.newBuilder().setOptimizerVersion("2").build(); + QueryOptions queryOptions = + QueryOptions.newBuilder() + .setOptimizerVersion("2") + .setOptimizerStatisticsPackage("custom-package") + .build(); QueryOptions defaultOptions = QueryOptions.getDefaultInstance(); DatabaseId db = DatabaseId.of("p", "i", "d"); DatabaseId otherDb = DatabaseId.of("p", "i", "other"); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java index 74ce8d7cb0..3698d3379f 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java @@ -54,6 +54,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.annotation.Nonnull; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -556,36 +557,80 @@ public void testSetEmulatorHostWithProtocol() { @Test public void testDefaultQueryOptions() { - SpannerOptions.useEnvironment(() -> ""); + SpannerOptions.useEnvironment( + new SpannerOptions.SpannerEnvironment() { + @Override + public String getOptimizerVersion() { + return ""; + } + + @Nonnull + @Override + public String getOptimizerStatisticsPackage() { + return ""; + } + }); SpannerOptions options = SpannerOptions.newBuilder() .setDefaultQueryOptions( DatabaseId.of("p", "i", "d"), - QueryOptions.newBuilder().setOptimizerVersion("1").build()) + QueryOptions.newBuilder() + .setOptimizerVersion("1") + .setOptimizerStatisticsPackage("custom-package") + .build()) .setProjectId("p") .setCredentials(NoCredentials.getInstance()) .build(); assertThat(options.getDefaultQueryOptions(DatabaseId.of("p", "i", "d"))) - .isEqualTo(QueryOptions.newBuilder().setOptimizerVersion("1").build()); + .isEqualTo( + QueryOptions.newBuilder() + .setOptimizerVersion("1") + .setOptimizerStatisticsPackage("custom-package") + .build()); assertThat(options.getDefaultQueryOptions(DatabaseId.of("p", "i", "o"))) .isEqualTo(QueryOptions.getDefaultInstance()); - // Now simulate that the user has set an environment variable for the query optimizer version. - SpannerOptions.useEnvironment(() -> "2"); - // Create options with '1' as the default query optimizer version. This should be overridden by + // Now simulate that the user has set an environment variable for the query optimizer version + // and statistics package. + SpannerOptions.useEnvironment( + new SpannerOptions.SpannerEnvironment() { + @Override + public String getOptimizerVersion() { + return "2"; + } + + @Nonnull + @Override + public String getOptimizerStatisticsPackage() { + return "env-package"; + } + }); + // Create options with '1' as the default query optimizer version and 'custom-package' as the + // default query optimizer statistics package. These values should be overridden by // the environment variable. options = SpannerOptions.newBuilder() .setDefaultQueryOptions( DatabaseId.of("p", "i", "d"), - QueryOptions.newBuilder().setOptimizerVersion("1").build()) + QueryOptions.newBuilder() + .setOptimizerVersion("1") + .setOptimizerStatisticsPackage("custom-package") + .build()) .setProjectId("p") .setCredentials(NoCredentials.getInstance()) .build(); assertThat(options.getDefaultQueryOptions(DatabaseId.of("p", "i", "d"))) - .isEqualTo(QueryOptions.newBuilder().setOptimizerVersion("2").build()); + .isEqualTo( + QueryOptions.newBuilder() + .setOptimizerVersion("2") + .setOptimizerStatisticsPackage("env-package") + .build()); assertThat(options.getDefaultQueryOptions(DatabaseId.of("p", "i", "o"))) - .isEqualTo(QueryOptions.newBuilder().setOptimizerVersion("2").build()); + .isEqualTo( + QueryOptions.newBuilder() + .setOptimizerVersion("2") + .setOptimizerStatisticsPackage("env-package") + .build()); } @Test diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractConnectionImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractConnectionImplTest.java index 7cfc4dd914..ca3d6313d7 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractConnectionImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractConnectionImplTest.java @@ -630,6 +630,51 @@ public void testGetOptimizerVersion() { } } + boolean isSetOptimizerStatisticsPackageAllowed() { + return !getConnection().isClosed(); + } + + @Test + public void testSetOptimizerStatisticsPackage() { + try (Connection connection = getConnection()) { + if (isSetOptimizerStatisticsPackageAllowed()) { + for (String statisticsPackage : new String[] {"custom-package", ""}) { + log("SET OPTIMIZER_STATISTICS_PACKAGE='" + statisticsPackage + "';"); + connection.setOptimizerStatisticsPackage(statisticsPackage); + + log("@EXPECT RESULT_SET 'OPTIMIZER_STATISTICS_PACKAGE','" + statisticsPackage + "'"); + log("SHOW VARIABLE OPTIMIZER_STATISTICS_PACKAGE;"); + assertThat(connection.getOptimizerStatisticsPackage(), is(equalTo(statisticsPackage))); + } + } else { + log("@EXPECT EXCEPTION FAILED_PRECONDITION"); + log("SET OPTIMIZER_STATISTICS_PACKAGE='custom-package';"); + exception.expect(matchCode(ErrorCode.FAILED_PRECONDITION)); + connection.setOptimizerStatisticsPackage("custom-package"); + } + } + } + + boolean isGetOptimizerStatisticsPackageAllowed() { + return !getConnection().isClosed(); + } + + @Test + public void testGetOptimizerStatisticsPackage() { + try (Connection connection = getConnection()) { + if (isGetOptimizerStatisticsPackageAllowed()) { + log("@EXPECT RESULT_SET 'OPTIMIZER_STATISTICS_PACKAGE'"); + log("SHOW VARIABLE OPTIMIZER_STATISTICS_PACKAGE;"); + assertThat(connection.getOptimizerStatisticsPackage(), is(notNullValue())); + } else { + log("@EXPECT EXCEPTION FAILED_PRECONDITION"); + log("SHOW VARIABLE OPTIMIZER_STATISTICS_PACKAGE;"); + exception.expect(matchCode(ErrorCode.FAILED_PRECONDITION)); + connection.getOptimizerStatisticsPackage(); + } + } + } + abstract boolean isCommitAllowed(); @Test diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplTest.java index a24231030b..8e5ed22454 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionImplTest.java @@ -607,6 +607,72 @@ public void testExecuteGetOptimizerVersion() { } } + @Test + public void testExecuteSetOptimizerStatisticsPackage() { + try (ConnectionImpl subject = + createConnection( + ConnectionOptions.newBuilder() + .setCredentials(NoCredentials.getInstance()) + .setUri(URI) + .build())) { + assertThat(subject.getOptimizerStatisticsPackage(), is(equalTo(""))); + + StatementResult res = + subject.execute(Statement.of("set optimizer_statistics_package='custom-package'")); + assertThat(res.getResultType(), is(equalTo(ResultType.NO_RESULT))); + assertThat(subject.getOptimizerStatisticsPackage(), is(equalTo("custom-package"))); + + res = subject.execute(Statement.of("set optimizer_statistics_package=''")); + assertThat(res.getResultType(), is(equalTo(ResultType.NO_RESULT))); + assertThat(subject.getOptimizerStatisticsPackage(), is(equalTo(""))); + } + } + + @Test + public void testExecuteSetOptimizerStatisticsPackageInvalidValue() { + try (ConnectionImpl subject = + createConnection( + ConnectionOptions.newBuilder() + .setCredentials(NoCredentials.getInstance()) + .setUri(URI) + .build())) { + assertThat(subject.getOptimizerVersion(), is(equalTo(""))); + + try { + subject.execute(Statement.of("set optimizer_statistics_package=' '")); + fail("Missing expected exception"); + } catch (SpannerException e) { + assertThat(e.getErrorCode(), is(equalTo(ErrorCode.INVALID_ARGUMENT))); + } + } + } + + @Test + public void testExecuteGetOptimizerStatisticsPackage() { + try (ConnectionImpl subject = + createConnection( + ConnectionOptions.newBuilder() + .setCredentials(NoCredentials.getInstance()) + .setUri(URI) + .build())) { + assertThat(subject.getOptimizerStatisticsPackage(), is(equalTo(""))); + + StatementResult res = + subject.execute(Statement.of("show variable optimizer_statistics_package")); + assertThat(res.getResultType(), is(equalTo(ResultType.RESULT_SET))); + assertThat(res.getResultSet().next(), is(true)); + assertThat(res.getResultSet().getString("OPTIMIZER_STATISTICS_PACKAGE"), is(equalTo(""))); + + subject.execute(Statement.of("set optimizer_statistics_package='custom-package'")); + res = subject.execute(Statement.of("show variable optimizer_statistics_package")); + assertThat(res.getResultType(), is(equalTo(ResultType.RESULT_SET))); + assertThat(res.getResultSet().next(), is(true)); + assertThat( + res.getResultSet().getString("OPTIMIZER_STATISTICS_PACKAGE"), + is(equalTo("custom-package"))); + } + } + @Test public void testExecuteSetReturnCommitStats() { try (ConnectionImpl subject = @@ -1297,55 +1363,81 @@ UnitOfWork getCurrentUnitOfWorkOrStartNewUnitOfWork() { return unitOfWork; } }) { - // Execute query with an optimizer version set on the connection. + // Execute query with an optimizer version and statistics package set on the connection. impl.setOptimizerVersion("1"); + impl.setOptimizerStatisticsPackage("custom-package-1"); impl.executeQuery(Statement.of("SELECT FOO FROM BAR")); verify(unitOfWork) .executeQueryAsync( StatementParser.INSTANCE.parse( Statement.newBuilder("SELECT FOO FROM BAR") - .withQueryOptions(QueryOptions.newBuilder().setOptimizerVersion("1").build()) + .withQueryOptions( + QueryOptions.newBuilder() + .setOptimizerVersion("1") + .setOptimizerStatisticsPackage("custom-package-1") + .build()) .build()), AnalyzeMode.NONE); - // Execute query with an optimizer version set on the connection. + // Execute query with an optimizer version and statistics package set on the connection. impl.setOptimizerVersion("2"); + impl.setOptimizerStatisticsPackage("custom-package-2"); impl.executeQuery(Statement.of("SELECT FOO FROM BAR")); verify(unitOfWork) .executeQueryAsync( StatementParser.INSTANCE.parse( Statement.newBuilder("SELECT FOO FROM BAR") - .withQueryOptions(QueryOptions.newBuilder().setOptimizerVersion("2").build()) + .withQueryOptions( + QueryOptions.newBuilder() + .setOptimizerVersion("2") + .setOptimizerStatisticsPackage("custom-package-2") + .build()) .build()), AnalyzeMode.NONE); - // Execute query with an optimizer version set on the connection and PrefetchChunks query + // Execute query with an optimizer version and statistics package set on the connection and + // PrefetchChunks query // option specified for the query. QueryOption prefetchOption = Options.prefetchChunks(100); impl.setOptimizerVersion("3"); + impl.setOptimizerStatisticsPackage("custom-package-3"); impl.executeQuery(Statement.of("SELECT FOO FROM BAR"), prefetchOption); verify(unitOfWork) .executeQueryAsync( StatementParser.INSTANCE.parse( Statement.newBuilder("SELECT FOO FROM BAR") - .withQueryOptions(QueryOptions.newBuilder().setOptimizerVersion("3").build()) + .withQueryOptions( + QueryOptions.newBuilder() + .setOptimizerVersion("3") + .setOptimizerStatisticsPackage("custom-package-3") + .build()) .build()), AnalyzeMode.NONE, prefetchOption); - // Execute query with an optimizer version set on the connection, and the same options also + // Execute query with an optimizer version and statistics package set on the connection, and + // the same options also // passed in to the query. The specific options passed in to the query should take precedence. impl.setOptimizerVersion("4"); + impl.setOptimizerStatisticsPackage("custom-package-4"); impl.executeQuery( Statement.newBuilder("SELECT FOO FROM BAR") - .withQueryOptions(QueryOptions.newBuilder().setOptimizerVersion("5").build()) + .withQueryOptions( + QueryOptions.newBuilder() + .setOptimizerVersion("5") + .setOptimizerStatisticsPackage("custom-package-5") + .build()) .build(), prefetchOption); verify(unitOfWork) .executeQueryAsync( StatementParser.INSTANCE.parse( Statement.newBuilder("SELECT FOO FROM BAR") - .withQueryOptions(QueryOptions.newBuilder().setOptimizerVersion("5").build()) + .withQueryOptions( + QueryOptions.newBuilder() + .setOptimizerVersion("5") + .setOptimizerStatisticsPackage("custom-package-5") + .build()) .build()), AnalyzeMode.NONE, prefetchOption); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorTest.java index d92b8cf265..41a87d31b9 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionStatementExecutorTest.java @@ -101,6 +101,12 @@ public void testStatementGetOptimizerVersion() { verify(connection).getOptimizerVersion(); } + @Test + public void testStatementGetOptimizerStatisticsPackage() { + subject.statementShowOptimizerStatisticsPackage(); + verify(connection).getOptimizerStatisticsPackage(); + } + @Test public void testStatementGetReadTimestamp() { subject.statementShowReadTimestamp(); @@ -183,6 +189,14 @@ public void testStatementSetOptimizerVersion() { verify(connection).setOptimizerVersion("LATEST"); } + @Test + public void testStatementSetOptimizerStatisticsPackage() { + subject.statementSetOptimizerStatisticsPackage("custom-package"); + verify(connection).setOptimizerStatisticsPackage("custom-package"); + subject.statementSetOptimizerStatisticsPackage(""); + verify(connection).setOptimizerStatisticsPackage(""); + } + @Test public void testStatementSetStatementTimeout() { subject.statementSetStatementTimeout(Duration.newBuilder().setNanos(100).build()); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionStatementWithNoParametersTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionStatementWithNoParametersTest.java index e3be3cfeae..84c6b8c8ce 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionStatementWithNoParametersTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionStatementWithNoParametersTest.java @@ -126,6 +126,21 @@ public void testExecuteGetOptimizerVersion() { verify(connection, times(1)).getOptimizerVersion(); } + @Test + public void testExecuteGetOptimizerStatisticsPackage() { + ParsedStatement statement = + parser.parse(Statement.of("show variable optimizer_statistics_package")); + ConnectionImpl connection = mock(ConnectionImpl.class); + ConnectionStatementExecutorImpl executor = mock(ConnectionStatementExecutorImpl.class); + when(executor.getConnection()).thenReturn(connection); + when(executor.statementShowOptimizerStatisticsPackage()).thenCallRealMethod(); + when(connection.getOptimizerStatisticsPackage()).thenReturn("custom-package"); + statement + .getClientSideStatement() + .execute(executor, "show variable optimizer_statistics_package"); + verify(connection, times(1)).getOptimizerStatisticsPackage(); + } + @Test public void testExecuteBegin() { ParsedStatement subject = parser.parse(Statement.of("begin")); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionStatementWithOneParameterTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionStatementWithOneParameterTest.java index d5d4ec34d2..cfb61154af 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionStatementWithOneParameterTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionStatementWithOneParameterTest.java @@ -160,6 +160,22 @@ public void testExecuteSetOptimizerVersion() { } } + @Test + public void testExecuteSetOptimizerStatisticsPackage() { + ParsedStatement subject = parser.parse(Statement.of("set optimizer_statistics_package='foo'")); + ConnectionImpl connection = mock(ConnectionImpl.class); + ConnectionStatementExecutorImpl executor = mock(ConnectionStatementExecutorImpl.class); + when(executor.getConnection()).thenReturn(connection); + when(executor.statementSetOptimizerStatisticsPackage(any(String.class))).thenCallRealMethod(); + for (String statisticsPackage : new String[] {"custom-package", ""}) { + subject + .getClientSideStatement() + .execute( + executor, String.format("set optimizer_statistics_package='%s'", statisticsPackage)); + verify(connection, times(1)).setOptimizerStatisticsPackage(statisticsPackage); + } + } + @Test public void testExecuteSetTransaction() { ParsedStatement subject = parser.parse(Statement.of("set transaction read_only")); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionTest.java index 38d182cc54..f21fbc7209 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionTest.java @@ -35,6 +35,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import javax.annotation.Nonnull; import org.junit.AfterClass; import org.junit.Test; import org.junit.experimental.runners.Enclosed; @@ -42,24 +43,24 @@ @RunWith(Enclosed.class) public class ConnectionTest { - - public static class DefaultConnectionOptionsTest extends AbstractMockServerTest { + public static class EnvironmentConnectionOptionsTest extends AbstractMockServerTest { @Test - public void testDefaultOptimizerVersion() { - try (Connection connection = createConnection()) { - try (ResultSet rs = - connection.executeQuery(Statement.of("SHOW VARIABLE OPTIMIZER_VERSION"))) { - assertThat(rs.next()).isTrue(); - assertThat(rs.getString("OPTIMIZER_VERSION")).isEqualTo(""); - assertThat(rs.next()).isFalse(); - } - } - } - - @Test - public void testUseOptimizerVersionFromEnvironment() { + public void testUseOptimizerVersionAndStatisticsPackageFromEnvironment() { try { - SpannerOptions.useEnvironment(() -> "20"); + SpannerOptions.useEnvironment( + new SpannerOptions.SpannerEnvironment() { + @Nonnull + @Override + public String getOptimizerVersion() { + return "20"; + } + + @Nonnull + @Override + public String getOptimizerStatisticsPackage() { + return "env-package"; + } + }); try (Connection connection = createConnection()) { // Do a query and verify that the version from the environment is used. try (ResultSet rs = connection.executeQuery(SELECT_COUNT_STATEMENT)) { @@ -69,10 +70,13 @@ public void testUseOptimizerVersionFromEnvironment() { // Verify query options from the environment. ExecuteSqlRequest request = getLastExecuteSqlRequest(); assertThat(request.getQueryOptions().getOptimizerVersion()).isEqualTo("20"); + assertThat(request.getQueryOptions().getOptimizerStatisticsPackage()) + .isEqualTo("env-package"); } // Now set one of the query options on the connection. That option should be used in // combination with the other option from the environment. connection.execute(Statement.of("SET OPTIMIZER_VERSION='30'")); + connection.execute(Statement.of("SET OPTIMIZER_STATISTICS_PACKAGE='custom-package'")); try (ResultSet rs = connection.executeQuery(SELECT_COUNT_STATEMENT)) { assertThat(rs.next()).isTrue(); assertThat(rs.getLong(0)).isEqualTo(COUNT_BEFORE_INSERT); @@ -81,6 +85,9 @@ public void testUseOptimizerVersionFromEnvironment() { ExecuteSqlRequest request = getLastExecuteSqlRequest(); // Optimizer version should come from the connection. assertThat(request.getQueryOptions().getOptimizerVersion()).isEqualTo("30"); + // Optimizer statistics package should come from the connection. + assertThat(request.getQueryOptions().getOptimizerStatisticsPackage()) + .isEqualTo("custom-package"); } // Now specify options directly for the query. These should override both the environment // and what is set on the connection. @@ -90,6 +97,7 @@ public void testUseOptimizerVersionFromEnvironment() { .withQueryOptions( QueryOptions.newBuilder() .setOptimizerVersion("user-defined-version") + .setOptimizerStatisticsPackage("user-defined-statistics-package") .build()) .build())) { assertThat(rs.next()).isTrue(); @@ -100,12 +108,41 @@ public void testUseOptimizerVersionFromEnvironment() { // Optimizer version should come from the query. assertThat(request.getQueryOptions().getOptimizerVersion()) .isEqualTo("user-defined-version"); + // Optimizer statistics package should come from the query. + assertThat(request.getQueryOptions().getOptimizerStatisticsPackage()) + .isEqualTo("user-defined-statistics-package"); } } } finally { SpannerOptions.useDefaultEnvironment(); } } + } + + public static class DefaultConnectionOptionsTest extends AbstractMockServerTest { + @Test + public void testDefaultOptimizerVersion() { + try (Connection connection = createConnection()) { + try (ResultSet rs = + connection.executeQuery(Statement.of("SHOW VARIABLE OPTIMIZER_VERSION"))) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("OPTIMIZER_VERSION")).isEqualTo(""); + assertThat(rs.next()).isFalse(); + } + } + } + + @Test + public void testDefaultOptimizerStatisticsPackage() { + try (Connection connection = createConnection()) { + try (ResultSet rs = + connection.executeQuery(Statement.of("SHOW VARIABLE OPTIMIZER_STATISTICS_PACKAGE"))) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("OPTIMIZER_STATISTICS_PACKAGE")).isEqualTo(""); + assertThat(rs.next()).isFalse(); + } + } + } @Test public void testExecuteInvalidBatchUpdate() { diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITQueryOptionsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITQueryOptionsTest.java new file mode 100644 index 0000000000..09f4ada43a --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITQueryOptionsTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.spanner.connection.it; + +import com.google.cloud.spanner.connection.ITAbstractSpannerTest; +import com.google.cloud.spanner.connection.SqlScriptVerifier; +import com.google.cloud.spanner.connection.SqlScriptVerifier.SpannerGenericConnection; +import org.junit.Before; +import org.junit.Test; + +public class ITQueryOptionsTest extends ITAbstractSpannerTest { + + private static final String TEST_QUERY_OPTIONS = "ITSqlScriptTest_TestQueryOptions.sql"; + + private SqlScriptVerifier verifier; + + @Before + public void setUp() { + verifier = new SqlScriptVerifier(); + } + + @Test + public void verifiesQueryOptions() throws Exception { + try (ITConnection connection = createConnection()) { + verifier.verifyStatementsInFile( + SpannerGenericConnection.of(connection), TEST_QUERY_OPTIONS, SqlScriptVerifier.class); + } + } +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITQueryOptionsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITQueryOptionsTest.java index 1ff035fca8..57aa77e6a7 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITQueryOptionsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITQueryOptionsTest.java @@ -59,12 +59,17 @@ public static void setUpDatabase() { @Test public void executeQuery() { // Version '1' should work. + // Statistics package 'custom-package' should work. try (ResultSet rs = client .singleUse() .executeQuery( Statement.newBuilder("SELECT 1") - .withQueryOptions(QueryOptions.newBuilder().setOptimizerVersion("1").build()) + .withQueryOptions( + QueryOptions.newBuilder() + .setOptimizerVersion("1") + .setOptimizerStatisticsPackage("custom-package") + .build()) .build())) { while (rs.next()) { assertThat(rs.getLong(0)).isEqualTo(1L); @@ -104,6 +109,7 @@ public void executeQuery() { @Test public void executeUpdate() { // Optimizer version 1 should work. + // Optimizer statistics package 'custom-package' should work. assertThat( client .readWriteTransaction() @@ -116,7 +122,10 @@ public void executeUpdate() { .bind("name") .to("One") .withQueryOptions( - QueryOptions.newBuilder().setOptimizerVersion("1").build()) + QueryOptions.newBuilder() + .setOptimizerVersion("1") + .setOptimizerStatisticsPackage("custom-package") + .build()) .build()))) .isEqualTo(1L); @@ -158,11 +167,15 @@ public void executeUpdate() { assertThat(e.getMessage()).contains("Query optimizer version: 100000 is not supported"); } - // Setting an optimizer version for PDML should also be allowed. + // Setting an optimizer version and statistics package for PDML should also be allowed. assertThat( client.executePartitionedUpdate( Statement.newBuilder("UPDATE TEST SET NAME='updated' WHERE 1=1") - .withQueryOptions(QueryOptions.newBuilder().setOptimizerVersion("1").build()) + .withQueryOptions( + QueryOptions.newBuilder() + .setOptimizerVersion("1") + .setOptimizerStatisticsPackage("custom-package") + .build()) .build())) .isEqualTo(2L); } @@ -170,12 +183,17 @@ public void executeUpdate() { @Test public void spannerOptions() { // Version '1' should work. + // Statistics package 'custom-package' should work. try (Spanner spanner = env.getTestHelper() .getOptions() .toBuilder() .setDefaultQueryOptions( - db.getId(), QueryOptions.newBuilder().setOptimizerVersion("1").build()) + db.getId(), + QueryOptions.newBuilder() + .setOptimizerVersion("1") + .setOptimizerStatisticsPackage("custom-package") + .build()) .build() .getService()) { DatabaseClient client = spanner.getDatabaseClient(db.getId()); diff --git a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ITSqlScriptTest_TestQueryOptions.sql b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ITSqlScriptTest_TestQueryOptions.sql index 923cc97167..7b1449cdf9 100644 --- a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ITSqlScriptTest_TestQueryOptions.sql +++ b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/ITSqlScriptTest_TestQueryOptions.sql @@ -15,10 +15,13 @@ */ /* - * Test setting and getting the optimizer version to use. + * Test setting and getting the following query options: + * - Optimizer version + * - Optimizer statistics package */ -- Set and get valid values. +-- Optimizer version @EXPECT NO_RESULT SET OPTIMIZER_VERSION = '1'; @@ -43,9 +46,27 @@ SET OPTIMIZER_VERSION = ''; @EXPECT RESULT_SET 'OPTIMIZER_VERSION','' SHOW VARIABLE OPTIMIZER_VERSION; +-- Optimizer statistics package +@EXPECT NO_RESULT +SET OPTIMIZER_STATISTICS_PACKAGE = 'custom-package_withNumbers-1234'; + +@EXPECT RESULT_SET 'OPTIMIZER_STATISTICS_PACKAGE','custom-package_withNumbers-1234' +SHOW VARIABLE OPTIMIZER_STATISTICS_PACKAGE; + +@EXPECT NO_RESULT +SET OPTIMIZER_STATISTICS_PACKAGE = ''; + +@EXPECT RESULT_SET 'OPTIMIZER_STATISTICS_PACKAGE','' +SHOW VARIABLE OPTIMIZER_STATISTICS_PACKAGE; + -- Try to set invalid values. +-- Optimizer version @EXPECT EXCEPTION INVALID_ARGUMENT 'INVALID_ARGUMENT: Unknown value for OPTIMIZER_VERSION: 'None'' SET OPTIMIZER_VERSION = 'None'; @EXPECT EXCEPTION INVALID_ARGUMENT 'INVALID_ARGUMENT: Unknown value for OPTIMIZER_VERSION: 'v1'' SET OPTIMIZER_VERSION = 'v1'; + +-- Optimizer statistics package +@EXPECT EXCEPTION INVALID_ARGUMENT 'INVALID_ARGUMENT: Unknown value for OPTIMIZER_STATISTICS_PACKAGE: ' '' +SET OPTIMIZER_STATISTICS_PACKAGE = ' ';