Skip to content

Commit c3be4d4

Browse files
authored
chore: cleanup statement execution and test all execute methods of Statement and PreparedStatement (#1321)
* test: add tests for all execute methods of Statement and PreparedStatement Adds tests for all the different variations of the execute method on Statement and PreparedStatement. This will allow us to make internal changes to the way these methods are handled, while keeping control over any behavior changes that it might introduce. * chore: share do-with-timeout code (#1324) Refactor the execute methods a bit so they all share the same do-with-timeout logic.
1 parent 90d4cc1 commit c3be4d4

File tree

3 files changed

+657
-50
lines changed

3 files changed

+657
-50
lines changed

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

+86-34
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,15 @@
2424
import com.google.cloud.spanner.connection.Connection;
2525
import com.google.cloud.spanner.connection.StatementResult;
2626
import com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType;
27+
import com.google.rpc.Code;
2728
import java.sql.ResultSet;
2829
import java.sql.SQLException;
2930
import java.sql.SQLWarning;
3031
import java.sql.Statement;
3132
import java.util.Arrays;
3233
import java.util.concurrent.TimeUnit;
34+
import java.util.function.Function;
35+
import java.util.function.Supplier;
3336

3437
/** Base class for Cloud Spanner JDBC {@link Statement}s */
3538
abstract class AbstractJdbcStatement extends AbstractJdbcWrapper implements Statement {
@@ -183,21 +186,74 @@ private ResultSet executeQuery(
183186
QueryAnalyzeMode analyzeMode,
184187
QueryOption... options)
185188
throws SQLException {
189+
Options.QueryOption[] queryOptions = getQueryOptions(options);
190+
return doWithStatementTimeout(
191+
() -> {
192+
com.google.cloud.spanner.ResultSet resultSet;
193+
if (analyzeMode == null) {
194+
resultSet = connection.getSpannerConnection().executeQuery(statement, queryOptions);
195+
} else {
196+
resultSet = connection.getSpannerConnection().analyzeQuery(statement, analyzeMode);
197+
}
198+
return JdbcResultSet.of(this, resultSet);
199+
});
200+
}
201+
202+
private <T> T doWithStatementTimeout(Supplier<T> runnable) throws SQLException {
203+
return doWithStatementTimeout(runnable, ignore -> Boolean.TRUE);
204+
}
205+
206+
private <T> T doWithStatementTimeout(
207+
Supplier<T> runnable, Function<T, Boolean> shouldResetTimeout) throws SQLException {
186208
StatementTimeout originalTimeout = setTemporaryStatementTimeout();
209+
T result = null;
187210
try {
188-
com.google.cloud.spanner.ResultSet resultSet;
189-
if (analyzeMode == null) {
190-
resultSet =
191-
connection.getSpannerConnection().executeQuery(statement, getQueryOptions(options));
192-
} else {
193-
resultSet = connection.getSpannerConnection().analyzeQuery(statement, analyzeMode);
194-
}
195-
return JdbcResultSet.of(this, resultSet);
196-
} catch (SpannerException e) {
197-
throw JdbcSqlExceptionFactory.of(e);
211+
result = runnable.get();
212+
return result;
213+
} catch (SpannerException spannerException) {
214+
throw JdbcSqlExceptionFactory.of(spannerException);
198215
} finally {
199-
resetStatementTimeout(originalTimeout);
216+
if (shouldResetTimeout.apply(result)) {
217+
resetStatementTimeout(originalTimeout);
218+
}
219+
}
220+
}
221+
222+
/**
223+
* Executes a SQL statement on the connection of this {@link Statement} as an update (DML)
224+
* statement.
225+
*
226+
* @param statement The SQL statement to execute
227+
* @return the number of rows that was inserted/updated/deleted
228+
* @throws SQLException if a database error occurs, or if the number of rows affected is larger
229+
* than {@link Integer#MAX_VALUE}
230+
*/
231+
int executeUpdate(com.google.cloud.spanner.Statement statement) throws SQLException {
232+
return checkedCast(executeLargeUpdate(statement));
233+
}
234+
235+
/**
236+
* Do a checked cast from long to int. Throws a {@link SQLException} with code {@link
237+
* Code#OUT_OF_RANGE} if the update count is too big to fit in an int.
238+
*/
239+
int checkedCast(long updateCount) throws SQLException {
240+
if (updateCount > Integer.MAX_VALUE) {
241+
throw JdbcSqlExceptionFactory.of(
242+
"update count too large for executeUpdate: " + updateCount, Code.OUT_OF_RANGE);
200243
}
244+
return (int) updateCount;
245+
}
246+
247+
/**
248+
* Executes a SQL statement on the connection of this {@link Statement} as an update (DML)
249+
* statement.
250+
*
251+
* @param statement The SQL statement to execute
252+
* @return the number of rows that was inserted/updated/deleted
253+
* @throws SQLException if a database error occurs
254+
*/
255+
long executeLargeUpdate(com.google.cloud.spanner.Statement statement) throws SQLException {
256+
return doWithStatementTimeout(() -> connection.getSpannerConnection().executeUpdate(statement));
201257
}
202258

203259
/**
@@ -210,24 +266,16 @@ private ResultSet executeQuery(
210266
* @throws SQLException if a database error occurs.
211267
*/
212268
StatementResult execute(com.google.cloud.spanner.Statement statement) throws SQLException {
213-
StatementTimeout originalTimeout = setTemporaryStatementTimeout();
214-
boolean mustResetTimeout = false;
215-
try {
216-
StatementResult result = connection.getSpannerConnection().execute(statement);
217-
mustResetTimeout = !resultIsSetStatementTimeout(result);
218-
if (mustResetTimeout && resultIsShowStatementTimeout(result)) {
219-
// it was a 'SHOW STATEMENT_TIMEOUT statement, we need to re-run to get the correct value
220-
mustResetTimeout = false;
221-
result = rerunShowStatementTimeout(statement, originalTimeout);
222-
}
223-
return result;
224-
} catch (SpannerException e) {
225-
throw JdbcSqlExceptionFactory.of(e);
226-
} finally {
227-
if (mustResetTimeout) {
228-
resetStatementTimeout(originalTimeout);
229-
}
269+
StatementResult statementResult =
270+
doWithStatementTimeout(
271+
() -> connection.getSpannerConnection().execute(statement),
272+
result -> !resultIsSetStatementTimeout(result));
273+
if (resultIsShowStatementTimeout(statementResult)) {
274+
// We can safely re-run it without first resetting the timeout to the original value, as that
275+
// has already been done by the 'doWithStatementTimeout' function.
276+
return rerunShowStatementTimeout(statement);
230277
}
278+
return statementResult;
231279
}
232280

233281
/**
@@ -250,18 +298,22 @@ StatementResult execute(com.google.cloud.spanner.Statement statement) throws SQL
250298
* executed was a SET STATEMENT_TIMEOUT statement.
251299
*/
252300
private boolean resultIsSetStatementTimeout(StatementResult result) {
253-
return result.getClientSideStatementType() == ClientSideStatementType.SET_STATEMENT_TIMEOUT;
301+
return result != null
302+
&& result.getClientSideStatementType() == ClientSideStatementType.SET_STATEMENT_TIMEOUT;
254303
}
255304

256305
private boolean resultIsShowStatementTimeout(StatementResult result) {
257-
return result.getClientSideStatementType() == ClientSideStatementType.SHOW_STATEMENT_TIMEOUT;
306+
return result != null
307+
&& result.getClientSideStatementType() == ClientSideStatementType.SHOW_STATEMENT_TIMEOUT;
258308
}
259309

260-
private StatementResult rerunShowStatementTimeout(
261-
com.google.cloud.spanner.Statement statement, StatementTimeout originalTimeout)
310+
private StatementResult rerunShowStatementTimeout(com.google.cloud.spanner.Statement statement)
262311
throws SQLException {
263-
resetStatementTimeout(originalTimeout);
264-
return connection.getSpannerConnection().execute(statement);
312+
try {
313+
return connection.getSpannerConnection().execute(statement);
314+
} catch (SpannerException spannerException) {
315+
throw JdbcSqlExceptionFactory.of(spannerException);
316+
}
265317
}
266318

267319
@Override

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

+3-16
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,7 @@ public int executeUpdate(String sql) throws SQLException {
7979

8080
private int executeUpdate(String sql, ImmutableList<String> generatedKeysColumns)
8181
throws SQLException {
82-
long result = executeLargeUpdate(sql, generatedKeysColumns);
83-
if (result > Integer.MAX_VALUE) {
84-
throw JdbcSqlExceptionFactory.of("update count too large: " + result, Code.OUT_OF_RANGE);
85-
}
86-
return (int) result;
82+
return checkedCast(executeLargeUpdate(sql, generatedKeysColumns));
8783
}
8884

8985
/**
@@ -300,11 +296,7 @@ public ResultSet getResultSet() throws SQLException {
300296
@Override
301297
public int getUpdateCount() throws SQLException {
302298
checkClosed();
303-
if (currentUpdateCount > Integer.MAX_VALUE) {
304-
throw JdbcSqlExceptionFactory.of(
305-
"update count too large: " + currentUpdateCount, Code.OUT_OF_RANGE);
306-
}
307-
return (int) currentUpdateCount;
299+
return checkedCast(currentUpdateCount);
308300
}
309301

310302
/**
@@ -481,12 +473,7 @@ private long[] executeBatch(boolean large) throws SQLException {
481473
int[] convertUpdateCounts(long[] updateCounts) throws SQLException {
482474
int[] res = new int[updateCounts.length];
483475
for (int index = 0; index < updateCounts.length; index++) {
484-
if (updateCounts[index] > Integer.MAX_VALUE) {
485-
throw JdbcSqlExceptionFactory.of(
486-
String.format("Update count too large for int: %d", updateCounts[index]),
487-
Code.OUT_OF_RANGE);
488-
}
489-
res[index] = (int) updateCounts[index];
476+
res[index] = checkedCast(updateCounts[index]);
490477
}
491478
return res;
492479
}

0 commit comments

Comments
 (0)