Skip to content

Commit 3c6eab4

Browse files
authored
fix: executeBatch did not respect statement timeout (#871)
The Statement#executeBatch() method did not respect the statement timeout that had been set on a statement. This meant that a batch like the following would always use the default timeout that is set in the Gapic generated client, and ignore the timeout set in Statement#setTimeout(int): statement.setTimeout(120); statement.addBatch("insert into foo (id) values (1)"); statement.executeBatch(); // This ignored the statement timeout Fixes #870
1 parent 1a9985a commit 3c6eab4

File tree

4 files changed

+130
-4
lines changed

4 files changed

+130
-4
lines changed

pom.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,10 @@
220220
<groupId>org.apache.maven.plugins</groupId>
221221
<artifactId>maven-surefire-plugin</artifactId>
222222
<configuration>
223-
<exclude>com.google.cloud.spanner.IntegrationTest</exclude>
223+
<excludes>
224+
<exclude>com.google.cloud.spanner.jdbc.it.**</exclude>
225+
<exclude>com.google.cloud.spanner.jdbc.JdbcStatementTimeoutTest</exclude>
226+
</excludes>
224227
<reportNameSuffix>sponge_log</reportNameSuffix>
225228
</configuration>
226229
</plugin>

src/main/java/com/google/cloud/spanner/jdbc/AbstractJdbcStatement.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ private TimeUnit getAppropriateTimeUnit() {
104104
* specified for a JDBC statement, and then after executing the JDBC statement setting the timeout
105105
* on the Spanner {@link Connection} again.
106106
*/
107-
private static class StatementTimeout {
107+
static class StatementTimeout {
108108
private final long timeout;
109109
private final TimeUnit unit;
110110

@@ -123,7 +123,7 @@ private StatementTimeout(long timeout, TimeUnit unit) {
123123
* {@link Statement} and returns the original timeout of the Spanner {@link Connection} so it can
124124
* be reset after the execution of a statement
125125
*/
126-
private StatementTimeout setTemporaryStatementTimeout() throws SQLException {
126+
StatementTimeout setTemporaryStatementTimeout() throws SQLException {
127127
StatementTimeout originalTimeout = null;
128128
if (getQueryTimeout() > 0) {
129129
if (connection.getSpannerConnection().hasStatementTimeout()) {
@@ -140,7 +140,7 @@ private StatementTimeout setTemporaryStatementTimeout() throws SQLException {
140140
* Resets the statement timeout of the Spanner {@link Connection} after a JDBC {@link Statement}
141141
* has been executed.
142142
*/
143-
private void resetStatementTimeout(StatementTimeout originalTimeout) throws SQLException {
143+
void resetStatementTimeout(StatementTimeout originalTimeout) throws SQLException {
144144
if (getQueryTimeout() > 0) {
145145
if (originalTimeout == null) {
146146
connection.getSpannerConnection().clearStatementTimeout();

src/main/java/com/google/cloud/spanner/jdbc/JdbcStatement.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ public long[] executeLargeBatch() throws SQLException {
265265
private long[] executeBatch(boolean large) throws SQLException {
266266
checkClosed();
267267
checkConnectionHasNoActiveBatch();
268+
StatementTimeout originalTimeout = setTemporaryStatementTimeout();
268269
try {
269270
switch (this.currentBatchType) {
270271
case DML:
@@ -310,6 +311,7 @@ private long[] executeBatch(boolean large) throws SQLException {
310311
String.format("Unknown batch type: %s", this.currentBatchType.name()));
311312
}
312313
} finally {
314+
resetStatementTimeout(originalTimeout);
313315
batchedStatements.clear();
314316
this.currentBatchType = BatchType.NONE;
315317
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* Copyright 2022 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.spanner.jdbc;
18+
19+
import static org.junit.Assert.assertArrayEquals;
20+
import static org.junit.Assert.assertEquals;
21+
import static org.junit.Assert.assertFalse;
22+
import static org.junit.Assert.assertThrows;
23+
24+
import com.google.cloud.spanner.MockSpannerServiceImpl.SimulatedExecutionTime;
25+
import com.google.cloud.spanner.connection.AbstractMockServerTest;
26+
import com.google.cloud.spanner.jdbc.JdbcSqlExceptionFactory.JdbcSqlTimeoutException;
27+
import java.sql.ResultSet;
28+
import java.sql.SQLException;
29+
import java.sql.Statement;
30+
import org.junit.Test;
31+
import org.junit.runner.RunWith;
32+
import org.junit.runners.JUnit4;
33+
34+
/**
35+
* Tests setting a statement timeout. This test is by default not included in unit test runs, as the
36+
* minimum timeout value in JDBC is 1 second, which again makes this test relatively slow.
37+
*/
38+
@RunWith(JUnit4.class)
39+
public class JdbcStatementTimeoutTest extends AbstractMockServerTest {
40+
41+
@Test
42+
public void testExecuteTimeout() throws SQLException {
43+
try (java.sql.Connection connection = createJdbcConnection()) {
44+
try (Statement statement = connection.createStatement()) {
45+
// First verify that execute does not time out by default.
46+
assertFalse(statement.execute(INSERT_STATEMENT.getSql()));
47+
int result = statement.getUpdateCount();
48+
assertEquals(1, result);
49+
50+
// Simulate that executeSql takes 2 seconds and set a statement timeout of 1 second.
51+
mockSpanner.setExecuteSqlExecutionTime(
52+
SimulatedExecutionTime.ofMinimumAndRandomTime(2000, 0));
53+
statement.setQueryTimeout(1);
54+
assertThrows(
55+
JdbcSqlTimeoutException.class, () -> statement.execute(INSERT_STATEMENT.getSql()));
56+
}
57+
}
58+
}
59+
60+
@Test
61+
public void testExecuteQueryTimeout() throws SQLException {
62+
try (java.sql.Connection connection = createJdbcConnection()) {
63+
try (Statement statement = connection.createStatement()) {
64+
// First verify that executeQuery does not time out by default.
65+
try (ResultSet resultSet = statement.executeQuery(SELECT_RANDOM_STATEMENT.getSql())) {
66+
int count = 0;
67+
while (resultSet.next()) {
68+
count++;
69+
}
70+
assertEquals(RANDOM_RESULT_SET_ROW_COUNT, count);
71+
}
72+
73+
// Simulate that executeStreamingSql takes 2 seconds and set a statement timeout of 1
74+
// second.
75+
mockSpanner.setExecuteStreamingSqlExecutionTime(
76+
SimulatedExecutionTime.ofMinimumAndRandomTime(2000, 0));
77+
statement.setQueryTimeout(1);
78+
assertThrows(
79+
JdbcSqlTimeoutException.class,
80+
() -> statement.executeQuery(SELECT_RANDOM_STATEMENT.getSql()));
81+
}
82+
}
83+
}
84+
85+
@Test
86+
public void testExecuteUpdateTimeout() throws SQLException {
87+
try (java.sql.Connection connection = createJdbcConnection()) {
88+
try (Statement statement = connection.createStatement()) {
89+
// First verify that executeUpdate does not time out by default.
90+
assertEquals(1, statement.executeUpdate(INSERT_STATEMENT.getSql()));
91+
92+
// Simulate that executeSql takes 2 seconds and set a statement timeout of 1 second.
93+
mockSpanner.setExecuteSqlExecutionTime(
94+
SimulatedExecutionTime.ofMinimumAndRandomTime(2000, 0));
95+
statement.setQueryTimeout(1);
96+
assertThrows(
97+
JdbcSqlTimeoutException.class,
98+
() -> statement.executeUpdate(INSERT_STATEMENT.getSql()));
99+
}
100+
}
101+
}
102+
103+
@Test
104+
public void testExecuteBatchTimeout() throws SQLException {
105+
try (java.sql.Connection connection = createJdbcConnection()) {
106+
try (Statement statement = connection.createStatement()) {
107+
// First verify that batch dml does not time out by default.
108+
statement.addBatch(INSERT_STATEMENT.getSql());
109+
int[] result = statement.executeBatch();
110+
assertArrayEquals(new int[] {1}, result);
111+
112+
// Simulate that executeBatchDml takes 2 seconds and set a statement timeout of 1 second.
113+
mockSpanner.setExecuteBatchDmlExecutionTime(
114+
SimulatedExecutionTime.ofMinimumAndRandomTime(2000, 0));
115+
statement.setQueryTimeout(1);
116+
statement.addBatch(INSERT_STATEMENT.getSql());
117+
assertThrows(JdbcSqlTimeoutException.class, statement::executeBatch);
118+
}
119+
}
120+
}
121+
}

0 commit comments

Comments
 (0)