22
33import static java .nio .charset .StandardCharsets .US_ASCII ;
44import static java .nio .charset .StandardCharsets .UTF_8 ;
5+ import static java .util .concurrent .TimeUnit .SECONDS ;
56import static org .duckdb .StatementReturnType .*;
67import static org .duckdb .io .IOUtils .*;
78
3738import java .util .ArrayList ;
3839import java .util .Calendar ;
3940import java .util .List ;
41+ import java .util .concurrent .ScheduledFuture ;
4042import java .util .concurrent .locks .Lock ;
4143import java .util .concurrent .locks .ReentrantLock ;
4244
@@ -59,6 +61,8 @@ public class DuckDBPreparedStatement implements PreparedStatement {
5961 private final List <String > batchedStatements = new ArrayList <>();
6062 private Boolean isBatch = false ;
6163 private Boolean isPreparedStatement = false ;
64+ private int queryTimeoutSeconds = 0 ;
65+ private ScheduledFuture <?> cancelQueryFuture = null ;
6266
6367 public DuckDBPreparedStatement (DuckDBConnection conn ) throws SQLException {
6468 if (conn == null ) {
@@ -180,7 +184,14 @@ private boolean execute(boolean startTransaction) throws SQLException {
180184 startTransaction ();
181185 }
182186
187+ if (queryTimeoutSeconds > 0 ) {
188+ cleanupCancelQueryTask ();
189+ cancelQueryFuture =
190+ DuckDBDriver .scheduler .schedule (new CancelQueryTask (), queryTimeoutSeconds , SECONDS );
191+ }
192+
183193 resultRef = DuckDBNative .duckdb_jdbc_execute (stmtRef , params );
194+ cleanupCancelQueryTask ();
184195 DuckDBResultSetMetaData resultMeta = DuckDBNative .duckdb_jdbc_query_result_meta (resultRef );
185196 selectResult = new DuckDBResultSet (conn , this , resultMeta , resultRef );
186197 returnsResultSet = resultMeta .return_type .equals (QUERY_RESULT );
@@ -356,6 +367,7 @@ public void close() throws SQLException {
356367 if (isClosed ()) {
357368 return ;
358369 }
370+ cleanupCancelQueryTask ();
359371 if (selectResult != null ) {
360372 selectResult .close ();
361373 selectResult = null ;
@@ -436,12 +448,16 @@ public void setEscapeProcessing(boolean enable) throws SQLException {
436448 @ Override
437449 public int getQueryTimeout () throws SQLException {
438450 checkOpen ();
439- return 0 ;
451+ return queryTimeoutSeconds ;
440452 }
441453
442454 @ Override
443455 public void setQueryTimeout (int seconds ) throws SQLException {
444456 checkOpen ();
457+ if (seconds < 0 ) {
458+ throw new SQLException ("Invalid negative timeout value: " + seconds );
459+ }
460+ this .queryTimeoutSeconds = seconds ;
445461 }
446462
447463 @ Override
@@ -1244,4 +1260,25 @@ private Lock getConnRefLock() throws SQLException {
12441260 throw new SQLException (e );
12451261 }
12461262 }
1263+
1264+ private void cleanupCancelQueryTask () {
1265+ if (cancelQueryFuture != null ) {
1266+ cancelQueryFuture .cancel (false );
1267+ cancelQueryFuture = null ;
1268+ }
1269+ }
1270+
1271+ private class CancelQueryTask implements Runnable {
1272+ @ Override
1273+ public void run () {
1274+ try {
1275+ if (DuckDBPreparedStatement .this .isClosed ()) {
1276+ return ;
1277+ }
1278+ DuckDBPreparedStatement .this .cancel ();
1279+ } catch (SQLException e ) {
1280+ // suppress
1281+ }
1282+ }
1283+ }
12471284}
0 commit comments