Skip to content

Commit 7209ce0

Browse files
committed
Support non-prepared queries execution (1.5)
This is a backport of the PR #555 to `v1.5-variegata` stable branch. Currently JDBC driver always uses prepare+execute even if the client calls `Statement#executeQuery()`, not the `Connection#prepareStatement()` along with `PreparedStatement#executeQuery()`. This can cause problems (side effects) like the one in #467. With this PR native prepared statement is only used when Java `PreparedStatement` is used. Pending query execution logic is used otherwise. Testing: tests added to cover concurrent closure for prepared and non-prepared statements separately. Ref: duckdblabs/duckdb-internal#6682
1 parent 58ea257 commit 7209ce0

12 files changed

Lines changed: 558 additions & 50 deletions

duckdb_java.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1destroy_1db_1ref
2929
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1create_1extension_1type
3030
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1disconnect
3131
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1execute
32+
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1execute_1pending
3233
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1fetch
3334
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1cast_1result_1to_1strings
3435
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1fetch_1size
@@ -38,11 +39,13 @@ Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1get_1catalog
3839
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1get_1profiling_1information
3940
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1get_1schema
4041
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1interrupt
42+
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1pending_1query
4143
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1prepare
4244
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1prepared_1statement_1meta
4345
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1query_1result_1meta
4446
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1query_1progress
4547
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1release
48+
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1release_1pending
4649
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1set_1auto_1commit
4750
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1set_1catalog
4851
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1set_1schema

duckdb_java.exp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ _Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1destroy_1db_1ref
2626
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1create_1extension_1type
2727
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1disconnect
2828
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1execute
29+
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1execute_1pending
2930
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1fetch
3031
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1cast_1result_1to_1strings
3132
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1fetch_1size
@@ -35,11 +36,13 @@ _Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1get_1catalog
3536
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1get_1profiling_1information
3637
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1get_1schema
3738
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1interrupt
39+
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1pending_1query
3840
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1prepare
3941
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1prepared_1statement_1meta
4042
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1query_1result_1meta
4143
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1query_1progress
4244
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1release
45+
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1release_1pending
4346
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1set_1auto_1commit
4447
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1set_1catalog
4548
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1set_1schema

duckdb_java.map

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ DUCKDB_JAVA {
2828
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1create_1extension_1type;
2929
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1disconnect;
3030
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1execute;
31+
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1execute_1pending;
3132
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1fetch;
3233
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1cast_1result_1to_1strings;
3334
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1fetch_1size;
@@ -37,11 +38,13 @@ DUCKDB_JAVA {
3738
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1get_1profiling_1information;
3839
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1get_1schema;
3940
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1interrupt;
41+
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1pending_1query;
4042
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1prepare;
4143
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1prepared_1statement_1meta;
4244
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1query_1result_1meta;
4345
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1query_1progress;
4446
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1release;
47+
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1release_1pending;
4548
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1set_1auto_1commit;
4649
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1set_1catalog;
4750
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1set_1schema;

src/jni/duckdb_java.cpp

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -212,24 +212,54 @@ jobject _duckdb_jdbc_prepare(JNIEnv *env, jclass, jobject conn_ref_buf, jbyteArr
212212
}
213213
}
214214

215-
auto stmt_ref = new StatementHolder();
215+
auto stmt_ref = make_uniq<StatementHolder>();
216216
stmt_ref->stmt = conn_ref->Prepare(std::move(statements.back()));
217217
if (stmt_ref->stmt->HasError()) {
218218
string error_msg = string(stmt_ref->stmt->GetError());
219219
stmt_ref->stmt = nullptr;
220-
221-
// No success, so it must be deleted
222-
delete stmt_ref;
223220
ThrowJNI(env, error_msg.c_str());
221+
return nullptr;
222+
}
223+
return env->NewDirectByteBuffer(stmt_ref.release(), 0);
224+
}
224225

225-
// Just return control flow back to JVM, as an Exception is pending anyway
226+
jobject _duckdb_jdbc_pending_query(JNIEnv *env, jclass, jobject conn_ref_buf, jbyteArray query_j) {
227+
auto conn_ref = get_connection(env, conn_ref_buf);
228+
if (!conn_ref) {
226229
return nullptr;
227230
}
228-
return env->NewDirectByteBuffer(stmt_ref, 0);
231+
232+
auto query = jbyteArray_to_string(env, query_j);
233+
234+
auto statements = conn_ref->ExtractStatements(query.c_str());
235+
if (statements.empty()) {
236+
throw InvalidInputException("No statements to execute.");
237+
}
238+
239+
// if there are multiple statements, we directly execute the statements besides the last one
240+
// we only return the result of the last statement to the user, unless one of the previous statements fails
241+
for (idx_t i = 0; i + 1 < statements.size(); i++) {
242+
auto res = conn_ref->Query(std::move(statements[i]));
243+
if (res->HasError()) {
244+
res->ThrowError();
245+
}
246+
}
247+
248+
Value result;
249+
bool stream_results =
250+
conn_ref->context->TryGetCurrentSetting("jdbc_stream_results", result) ? result.GetValue<bool>() : false;
251+
QueryParameters query_parameters;
252+
query_parameters.output_type =
253+
stream_results ? QueryResultOutputType::ALLOW_STREAMING : QueryResultOutputType::FORCE_MATERIALIZED;
254+
255+
auto pending_ref = make_uniq<PendingHolder>();
256+
pending_ref->pending = conn_ref->PendingQuery(std::move(statements.back()), query_parameters);
257+
258+
return env->NewDirectByteBuffer(pending_ref.release(), 0);
229259
}
230260

231261
jobject _duckdb_jdbc_execute(JNIEnv *env, jclass, jobject stmt_ref_buf, jobjectArray params) {
232-
auto stmt_ref = (StatementHolder *)env->GetDirectBufferAddress(stmt_ref_buf);
262+
auto stmt_ref = reinterpret_cast<StatementHolder *>(env->GetDirectBufferAddress(stmt_ref_buf));
233263
if (!stmt_ref) {
234264
throw InvalidInputException("Invalid statement");
235265
}
@@ -269,21 +299,50 @@ jobject _duckdb_jdbc_execute(JNIEnv *env, jclass, jobject stmt_ref_buf, jobjectA
269299
return env->NewDirectByteBuffer(res_ref.release(), 0);
270300
}
271301

302+
jobject _duckdb_jdbc_execute_pending(JNIEnv *env, jclass, jobject pending_ref_buf) {
303+
auto pending_ref = reinterpret_cast<PendingHolder *>(env->GetDirectBufferAddress(pending_ref_buf));
304+
if (!pending_ref) {
305+
throw InvalidInputException("Invalid pending query");
306+
}
307+
308+
auto res_ref = make_uniq<ResultHolder>();
309+
res_ref->res = pending_ref->pending->Execute();
310+
if (res_ref->res->HasError()) {
311+
std::string error_msg = std::string(res_ref->res->GetError());
312+
duckdb::ExceptionType error_type = res_ref->res->GetErrorType();
313+
res_ref->res = nullptr;
314+
jclass exc_type = duckdb::ExceptionType::INTERRUPT == error_type ? J_SQLTimeoutException : J_SQLException;
315+
env->ThrowNew(exc_type, error_msg.c_str());
316+
return nullptr;
317+
}
318+
return env->NewDirectByteBuffer(res_ref.release(), 0);
319+
}
320+
272321
void _duckdb_jdbc_release(JNIEnv *env, jclass, jobject stmt_ref_buf) {
273322
if (nullptr == stmt_ref_buf) {
274323
return;
275324
}
276-
auto stmt_ref = (StatementHolder *)env->GetDirectBufferAddress(stmt_ref_buf);
325+
auto stmt_ref = reinterpret_cast<StatementHolder *>(env->GetDirectBufferAddress(stmt_ref_buf));
277326
if (stmt_ref) {
278327
delete stmt_ref;
279328
}
280329
}
281330

331+
void _duckdb_jdbc_release_pending(JNIEnv *env, jclass, jobject pending_ref_buf) {
332+
if (nullptr == pending_ref_buf) {
333+
return;
334+
}
335+
auto pending_ref = reinterpret_cast<PendingHolder *>(env->GetDirectBufferAddress(pending_ref_buf));
336+
if (pending_ref) {
337+
delete pending_ref;
338+
}
339+
}
340+
282341
void _duckdb_jdbc_free_result(JNIEnv *env, jclass, jobject res_ref_buf) {
283342
if (nullptr == res_ref_buf) {
284343
return;
285344
}
286-
auto res_ref = (ResultHolder *)env->GetDirectBufferAddress(res_ref_buf);
345+
auto res_ref = reinterpret_cast<ResultHolder *>(env->GetDirectBufferAddress(res_ref_buf));
287346
if (res_ref) {
288347
delete res_ref;
289348
}

src/jni/functions.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,17 @@ JNIEXPORT jobject JNICALL Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1prepare(JNI
131131
}
132132
}
133133

134+
JNIEXPORT jobject JNICALL Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1pending_1query(JNIEnv * env, jclass param0, jobject param1, jbyteArray param2) {
135+
try {
136+
return _duckdb_jdbc_pending_query(env, param0, param1, param2);
137+
} catch (const std::exception &e) {
138+
duckdb::ErrorData error(e);
139+
ThrowJNI(env, error.Message().c_str());
140+
141+
return nullptr;
142+
}
143+
}
144+
134145
JNIEXPORT void JNICALL Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1release(JNIEnv * env, jclass param0, jobject param1) {
135146
try {
136147
return _duckdb_jdbc_release(env, param0, param1);
@@ -141,6 +152,16 @@ JNIEXPORT void JNICALL Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1release(JNIEnv
141152
}
142153
}
143154

155+
JNIEXPORT void JNICALL Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1release_1pending(JNIEnv * env, jclass param0, jobject param1) {
156+
try {
157+
_duckdb_jdbc_release_pending(env, param0, param1);
158+
} catch (const std::exception &e) {
159+
duckdb::ErrorData error(e);
160+
ThrowJNI(env, error.Message().c_str());
161+
162+
}
163+
}
164+
144165
JNIEXPORT jobject JNICALL Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1query_1result_1meta(JNIEnv * env, jclass param0, jobject param1) {
145166
try {
146167
return _duckdb_jdbc_query_result_meta(env, param0, param1);
@@ -174,6 +195,17 @@ JNIEXPORT jobject JNICALL Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1execute(JNI
174195
}
175196
}
176197

198+
JNIEXPORT jobject JNICALL Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1execute_1pending(JNIEnv * env, jclass param0, jobject param1) {
199+
try {
200+
return _duckdb_jdbc_execute_pending(env, param0, param1);
201+
} catch (const std::exception &e) {
202+
duckdb::ErrorData error(e);
203+
ThrowJNI(env, error.Message().c_str());
204+
205+
return nullptr;
206+
}
207+
}
208+
177209
JNIEXPORT void JNICALL Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1free_1result(JNIEnv * env, jclass param0, jobject param1) {
178210
try {
179211
return _duckdb_jdbc_free_result(env, param0, param1);

src/jni/functions.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,18 @@ jobject _duckdb_jdbc_prepare(JNIEnv * env, jclass param0, jobject param1, jbyteA
5757

5858
JNIEXPORT jobject JNICALL Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1prepare(JNIEnv * env, jclass param0, jobject param1, jbyteArray param2);
5959

60+
jobject _duckdb_jdbc_pending_query(JNIEnv * env, jclass param0, jobject param1, jbyteArray param2);
61+
62+
JNIEXPORT jobject JNICALL Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1pending_1query(JNIEnv * env, jclass param0, jobject param1, jbyteArray param2);
63+
6064
void _duckdb_jdbc_release(JNIEnv * env, jclass param0, jobject param1);
6165

6266
JNIEXPORT void JNICALL Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1release(JNIEnv * env, jclass param0, jobject param1);
6367

68+
void _duckdb_jdbc_release_pending(JNIEnv * env, jclass param0, jobject param1);
69+
70+
JNIEXPORT void JNICALL Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1release_1pending(JNIEnv * env, jclass param0, jobject param1);
71+
6472
jobject _duckdb_jdbc_query_result_meta(JNIEnv * env, jclass param0, jobject param1);
6573

6674
JNIEXPORT jobject JNICALL Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1query_1result_1meta(JNIEnv * env, jclass param0, jobject param1);
@@ -73,6 +81,10 @@ jobject _duckdb_jdbc_execute(JNIEnv * env, jclass param0, jobject param1, jobjec
7381

7482
JNIEXPORT jobject JNICALL Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1execute(JNIEnv * env, jclass param0, jobject param1, jobjectArray param2);
7583

84+
jobject _duckdb_jdbc_execute_pending(JNIEnv * env, jclass param0, jobject param1);
85+
86+
JNIEXPORT jobject JNICALL Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1execute_1pending(JNIEnv * env, jclass param0, jobject param1);
87+
7688
void _duckdb_jdbc_free_result(JNIEnv * env, jclass param0, jobject param1);
7789

7890
JNIEXPORT void JNICALL Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1free_1result(JNIEnv * env, jclass param0, jobject param1);

src/jni/holders.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ struct StatementHolder {
4646
duckdb::unique_ptr<duckdb::PreparedStatement> stmt;
4747
};
4848

49+
struct PendingHolder {
50+
duckdb::unique_ptr<duckdb::PendingQueryResult> pending;
51+
};
52+
4953
struct ResultHolder {
5054
duckdb::unique_ptr<duckdb::QueryResult> res;
5155
duckdb::unique_ptr<duckdb::DataChunk> chunk;

src/main/java/org/duckdb/DuckDBConnection.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public final class DuckDBConnection implements java.sql.Connection {
3737

3838
ByteBuffer connRef;
3939
final ReentrantLock connRefLock = new ReentrantLock();
40+
final LinkedHashSet<DuckDBPendingQuery> pendingQueries = new LinkedHashSet<>();
4041
final LinkedHashSet<DuckDBPreparedStatement> preparedStatements = new LinkedHashSet<>();
4142
final LinkedHashSet<DuckDBAppender> appenders = new LinkedHashSet<>();
4243
volatile boolean closing;
@@ -145,6 +146,14 @@ public void close() throws SQLException {
145146
// suppress
146147
}
147148

149+
// Last pending query created is first deleted
150+
List<DuckDBPendingQuery> pendingList = new ArrayList<>(pendingQueries);
151+
Collections.reverse(pendingList);
152+
for (DuckDBPendingQuery pending : pendingList) {
153+
pending.close();
154+
}
155+
pendingQueries.clear();
156+
148157
// Last statement created is first deleted
149158
List<DuckDBPreparedStatement> psList = new ArrayList<>(preparedStatements);
150159
Collections.reverse(psList);

src/main/java/org/duckdb/DuckDBNative.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@ private static void loadFromCurrentJarDir(String libName) throws Exception {
173173
// returns res_ref result reference object
174174
static native ByteBuffer duckdb_jdbc_execute(ByteBuffer stmt_ref, Object[] params) throws SQLException;
175175

176+
static native ByteBuffer duckdb_jdbc_pending_query(ByteBuffer conn_ref, byte[] query) throws SQLException;
177+
178+
static native ByteBuffer duckdb_jdbc_execute_pending(ByteBuffer pending_ref) throws SQLException;
179+
180+
static native void duckdb_jdbc_release_pending(ByteBuffer pending_ref) throws SQLException;
181+
176182
static native void duckdb_jdbc_free_result(ByteBuffer res_ref);
177183

178184
static native DuckDBVector[] duckdb_jdbc_fetch(ByteBuffer res_ref, ByteBuffer conn_ref) throws SQLException;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package org.duckdb;
2+
3+
import java.nio.ByteBuffer;
4+
import java.sql.SQLException;
5+
import java.util.concurrent.locks.ReentrantLock;
6+
7+
class DuckDBPendingQuery {
8+
private DuckDBConnection conn;
9+
ByteBuffer pendingRef = null;
10+
final ReentrantLock pendingRefLock = new ReentrantLock();
11+
12+
DuckDBPendingQuery(DuckDBConnection conn, ByteBuffer pendingRef) {
13+
this.conn = conn;
14+
this.pendingRef = pendingRef;
15+
this.conn.connRefLock.lock();
16+
try {
17+
this.conn.pendingQueries.add(this);
18+
} finally {
19+
this.conn.connRefLock.unlock();
20+
}
21+
}
22+
23+
void close() throws SQLException {
24+
if (pendingRef == null) {
25+
return;
26+
}
27+
pendingRefLock.lock();
28+
try {
29+
if (pendingRef == null) {
30+
return;
31+
}
32+
DuckDBNative.duckdb_jdbc_release_pending(pendingRef);
33+
pendingRef = null;
34+
} finally {
35+
pendingRefLock.unlock();
36+
}
37+
38+
// Untrack pending query from parent connection,
39+
// if 'closing' flag is set it means that the parent connection itself
40+
// is being closed and we don't need to untrack this instance
41+
if (!conn.closing) {
42+
conn.connRefLock.lock();
43+
try {
44+
conn.pendingQueries.remove(this);
45+
} finally {
46+
conn.connRefLock.unlock();
47+
}
48+
}
49+
}
50+
}

0 commit comments

Comments
 (0)