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 f7214ea7..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,6 +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. *
  • 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 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(