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(