2424import com .google .cloud .spanner .connection .Connection ;
2525import com .google .cloud .spanner .connection .StatementResult ;
2626import com .google .cloud .spanner .connection .StatementResult .ClientSideStatementType ;
27+ import com .google .rpc .Code ;
2728import java .sql .ResultSet ;
2829import java .sql .SQLException ;
2930import java .sql .SQLWarning ;
3031import java .sql .Statement ;
3132import java .util .Arrays ;
3233import 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 */
3538abstract 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
0 commit comments