From fd103d488278b071c7322cc0726f2d855e79e97c Mon Sep 17 00:00:00 2001 From: Olav Loite Date: Tue, 26 Jan 2021 16:59:38 +0100 Subject: [PATCH 1/2] feat: allow setting min/max sessions --- .../google/cloud/spanner/jdbc/JdbcDriver.java | 2 + .../spanner/jdbc/JdbcConnectionUrlTest.java | 121 ++++++++++++++++++ .../cloud/spanner/jdbc/JdbcGrpcErrorTest.java | 21 +-- 3 files changed, 128 insertions(+), 16 deletions(-) create mode 100644 src/test/java/com/google/cloud/spanner/jdbc/JdbcConnectionUrlTest.java diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java index d2a2fea2..2c9350f3 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java @@ -88,6 +88,8 @@ * connection. Default is true. @see {@link * com.google.cloud.spanner.jdbc.CloudSpannerJdbcConnection#setRetryAbortsInternally(boolean)} * for more information. + *
  • minSessions (int): Sets the minimum number of sessions in the backing session pool. Defaults to 100. + *
  • maxSessions (int): Sets the maximum number of sessions in the backing session pool. Defaults to 400. *
  • numChannels (int): Sets the number of gRPC channels to use. Defaults to 4. *
  • usePlainText (boolean): Sets whether the JDBC connection should establish an unencrypted connection to the server. This option can only be used when connecting to a local emulator that does not require an encrypted connection, and that does not require authentication. *
  • optimizerVersion (string): The query optimizer version to use for the connection. The value must be either a valid version number or LATEST. If no value is specified, the query optimizer version specified in the environment variable SPANNER_OPTIMIZER_VERSION will be used. If no query optimizer version is specified in the connection URL or in the environment variable, the default query optimizer version of Cloud Spanner will be used. diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcConnectionUrlTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcConnectionUrlTest.java new file mode 100644 index 00000000..64ebe955 --- /dev/null +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcConnectionUrlTest.java @@ -0,0 +1,121 @@ +/* + * Copyright 2021 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.jdbc; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.cloud.spanner.connection.AbstractMockServerTest; +import com.google.common.base.Predicate; +import com.google.protobuf.AbstractMessage; +import com.google.spanner.v1.BatchCreateSessionsRequest; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.experimental.runners.Enclosed; +import org.junit.runner.RunWith; + +@RunWith(Enclosed.class) +public class JdbcConnectionUrlTest { + + public static class ConnectionMinSessionsTest extends AbstractMockServerTest { + @AfterClass + public static void reset() { + mockSpanner.reset(); + } + + protected String getBaseUrl() { + return super.getBaseUrl() + ";minSessions=1"; + } + + @Test + public void testMinSessions() throws InterruptedException, TimeoutException, SQLException { + try (Connection connection = createJdbcConnection()) { + mockSpanner.waitForRequestsToContain( + new Predicate() { + @Override + public boolean apply(AbstractMessage input) { + return input instanceof BatchCreateSessionsRequest + && ((BatchCreateSessionsRequest) input).getSessionCount() == 1; + } + }, + 5000L); + } + } + } + + public static class ConnectionMaxSessionsTest extends AbstractMockServerTest { + + @AfterClass + public static void reset() { + mockSpanner.reset(); + } + + protected String getBaseUrl() { + return super.getBaseUrl() + ";maxSessions=1"; + } + + @Test + public void testMaxSessions() + throws InterruptedException, TimeoutException, ExecutionException, SQLException { + ExecutorService executor1 = Executors.newSingleThreadExecutor(); + ExecutorService executor2 = Executors.newSingleThreadExecutor(); + + try (Connection connection1 = createJdbcConnection(); + Connection connection2 = createJdbcConnection()) { + final CountDownLatch latch = new CountDownLatch(1); + Future fut1 = + executor1.submit( + new Callable() { + @Override + public Void call() throws SQLException, InterruptedException { + latch.await(5L, TimeUnit.SECONDS); + connection1.createStatement().executeUpdate(INSERT_STATEMENT.getSql()); + connection1.commit(); + return null; + } + }); + Future fut2 = + executor2.submit( + new Callable() { + @Override + public Void call() throws SQLException { + latch.countDown(); + connection2.createStatement().executeUpdate(INSERT_STATEMENT.getSql()); + connection2.commit(); + return null; + } + }); + // Wait until both finishes. + fut1.get(5L, TimeUnit.SECONDS); + fut2.get(5L, TimeUnit.SECONDS); + } finally { + executor1.shutdown(); + executor2.shutdown(); + } + assertThat(mockSpanner.numSessionsCreated()).isEqualTo(1); + } + } +} diff --git a/src/test/java/com/google/cloud/spanner/jdbc/JdbcGrpcErrorTest.java b/src/test/java/com/google/cloud/spanner/jdbc/JdbcGrpcErrorTest.java index bdeda19c..51e9ab07 100644 --- a/src/test/java/com/google/cloud/spanner/jdbc/JdbcGrpcErrorTest.java +++ b/src/test/java/com/google/cloud/spanner/jdbc/JdbcGrpcErrorTest.java @@ -46,7 +46,6 @@ import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -133,7 +132,7 @@ public void reset() { private String createUrl() { return String.format( - "jdbc:cloudspanner://localhost:%d/projects/%s/instances/%s/databases/%s?usePlainText=true", + "jdbc:cloudspanner://localhost:%d/projects/%s/instances/%s/databases/%s?usePlainText=true;minSessions=0", server.getPort(), "proj", "inst", "db"); } @@ -141,13 +140,13 @@ private Connection createConnection() throws SQLException { return DriverManager.getConnection(createUrl()); } - @Ignore( - "This can only be guaranteed with MinSessions=0. Re-enable when MinSessions is configurable for JDBC.") @Test public void autocommitBeginTransaction() { mockSpanner.setBeginTransactionExecutionTime( SimulatedExecutionTime.ofException(serverException)); try (java.sql.Connection connection = createConnection()) { + // This triggers a retry with an explicit BeginTransaction RPC. + mockSpanner.abortNextStatement(); connection.createStatement().executeUpdate(UPDATE_STATEMENT.getSql()); fail("missing expected exception"); } catch (SQLException e) { @@ -155,8 +154,6 @@ public void autocommitBeginTransaction() { } } - @Ignore( - "This can only be guaranteed with MinSessions=0. Re-enable when MinSessions is configurable for JDBC.") @Test public void autocommitBeginPDMLTransaction() { mockSpanner.setBeginTransactionExecutionTime( @@ -170,14 +167,14 @@ public void autocommitBeginPDMLTransaction() { } } - @Ignore( - "This can only be guaranteed with MinSessions=0. Re-enable when MinSessions is configurable for JDBC.") @Test public void transactionalBeginTransaction() { mockSpanner.setBeginTransactionExecutionTime( SimulatedExecutionTime.ofException(serverException)); try (java.sql.Connection connection = createConnection()) { connection.setAutoCommit(false); + // This triggers a retry with an explicit BeginTransaction RPC. + mockSpanner.abortNextStatement(); connection.createStatement().executeUpdate(UPDATE_STATEMENT.getSql()); fail("missing expected exception"); } catch (SQLException e) { @@ -185,8 +182,6 @@ public void transactionalBeginTransaction() { } } - @Ignore( - "This can only be guaranteed with MinSessions=0. Re-enable when MinSessions is configurable for JDBC.") @Test public void readOnlyBeginTransaction() { mockSpanner.setBeginTransactionExecutionTime( @@ -368,8 +363,6 @@ public void readOnlyExecuteStreamingSql() { } } - @Ignore( - "This can only be guaranteed with MinSessions=0. Re-enable when MinSessions is configurable for JDBC.") @Test public void autocommitCreateSession() { mockSpanner.setBatchCreateSessionsExecutionTime( @@ -382,8 +375,6 @@ public void autocommitCreateSession() { } } - @Ignore( - "This can only be guaranteed with MinSessions=0. Re-enable when MinSessions is configurable for JDBC.") @Test public void transactionalCreateSession() { mockSpanner.setBatchCreateSessionsExecutionTime( @@ -397,8 +388,6 @@ public void transactionalCreateSession() { } } - @Ignore( - "This can only be guaranteed with MinSessions=0. Re-enable when MinSessions is configurable for JDBC.") @Test public void readOnlyCreateSession() { mockSpanner.setBatchCreateSessionsExecutionTime( From e292ad1cdba12effefc50a7c367bdf7d896da9ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Wed, 17 Feb 2021 20:43:23 +0100 Subject: [PATCH 2/2] chore: run code formatter --- src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java index 9619610c..3f4a4335 100644 --- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java +++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java @@ -88,8 +88,10 @@ * connection. Default is true. @see {@link * com.google.cloud.spanner.jdbc.CloudSpannerJdbcConnection#setRetryAbortsInternally(boolean)} * for more information. - *
  • minSessions (int): Sets the minimum number of sessions in the backing session pool. Defaults to 100. - *
  • maxSessions (int): Sets the maximum number of sessions in the backing session pool. Defaults to 400. + *
  • minSessions (int): Sets the minimum number of sessions in the backing session pool. + * Defaults to 100. + *
  • maxSessions (int): Sets the maximum number of sessions in the backing session pool. + * Defaults to 400. *
  • numChannels (int): Sets the number of gRPC channels to use. Defaults to 4. *
  • usePlainText (boolean): Sets whether the JDBC connection should establish an unencrypted * connection to the server. This option can only be used when connecting to a local emulator