Skip to content

Commit 543e5ac

Browse files
committed
Use native string cast for ext composite types
This change adjusts the `ResultSet#getString()` behaviour for columns with composite types like `STRUCT` or `LIST` and also for extension types. This method now delegates the cast to the native cast to `VARCHAR`. When the extension (e.g. `spatial`) registers a cast for its own types to `VARCHAR` - this cast now will be triggered. In many cases, like `MAP`s, the resulting representation is the same. But in some cases, like `spatial`'s `GEOMETRY` the resulting string now can be correctly deserialized into its textual representation: ``` POINT (41.1 42.2) ``` instead of opaque (and useless one): ``` DuckDBBlobResult{buffer=java.nio.HeapByteBuffer[pos=0 lim=32 cap=32]} ``` Fixes: #298
1 parent b75d4ca commit 543e5ac

File tree

11 files changed

+160
-9
lines changed

11 files changed

+160
-9
lines changed

duckdb_java.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ 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
3232
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1fetch
33+
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1cast_1result_1to_1strings
3334
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1fetch_1size
3435
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1free_1result
3536
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1get_1auto_1commit

duckdb_java.exp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ _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
2929
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1fetch
30+
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1cast_1result_1to_1strings
3031
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1fetch_1size
3132
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1free_1result
3233
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1get_1auto_1commit

duckdb_java.map

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ DUCKDB_JAVA {
2929
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1disconnect;
3030
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1execute;
3131
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1fetch;
32+
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1cast_1result_1to_1strings;
3233
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1fetch_1size;
3334
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1free_1result;
3435
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1get_1auto_1commit;

src/jni/duckdb_java.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,40 @@ jobjectArray _duckdb_jdbc_fetch(JNIEnv *env, jclass, jobject res_ref_buf, jobjec
399399
return vec_array;
400400
}
401401

402+
jobjectArray _duckdb_jdbc_cast_result_to_strings(JNIEnv *env, jclass, jobject res_ref_buf, jobject conn_ref_buf,
403+
jlong col_idx) {
404+
auto res_ref = reinterpret_cast<ResultHolder *>(env->GetDirectBufferAddress(res_ref_buf));
405+
if (!res_ref || !res_ref->res || res_ref->res->HasError()) {
406+
throw InvalidInputException("Invalid result set");
407+
}
408+
409+
if (!res_ref->chunk) {
410+
return nullptr;
411+
}
412+
413+
auto conn_ref = get_connection(env, conn_ref_buf);
414+
if (conn_ref == nullptr) {
415+
return nullptr;
416+
}
417+
418+
auto row_count = res_ref->chunk->size();
419+
auto &complex_vec = res_ref->chunk->data[col_idx];
420+
Vector vec(LogicalType::VARCHAR);
421+
VectorOperations::Cast(*conn_ref->context, complex_vec, vec, row_count);
422+
423+
jobjectArray string_data = env->NewObjectArray(row_count, J_String, nullptr);
424+
for (idx_t row_idx = 0; row_idx < row_count; row_idx++) {
425+
if (FlatVector::IsNull(vec, row_idx)) {
426+
continue;
427+
}
428+
auto d_str = (reinterpret_cast<string_t *>(FlatVector::GetData(vec)))[row_idx];
429+
auto j_str = decode_charbuffer_to_jstring(env, d_str.GetData(), d_str.GetSize());
430+
env->SetObjectArrayElement(string_data, row_idx, j_str);
431+
}
432+
433+
return string_data;
434+
}
435+
402436
jobject ProcessVector(JNIEnv *env, Connection *conn_ref, Vector &vec, idx_t row_count) {
403437
auto type_str = env->NewStringUTF(type_to_jduckdb_type(vec.GetType()).c_str());
404438
// construct nullmask

src/jni/functions.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,17 @@ JNIEXPORT jobjectArray JNICALL Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1fetch(
195195
}
196196
}
197197

198+
JNIEXPORT jobjectArray JNICALL Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1cast_1result_1to_1strings(JNIEnv * env, jclass param0, jobject param1, jobject param2, jlong param3) {
199+
try {
200+
return _duckdb_jdbc_cast_result_to_strings(env, param0, param1, param2, param3);
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+
198209
JNIEXPORT jint JNICALL Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1fetch_1size(JNIEnv * env, jclass param0) {
199210
try {
200211
return _duckdb_jdbc_fetch_size(env, param0);

src/jni/functions.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ jobjectArray _duckdb_jdbc_fetch(JNIEnv * env, jclass param0, jobject param1, job
8181

8282
JNIEXPORT jobjectArray JNICALL Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1fetch(JNIEnv * env, jclass param0, jobject param1, jobject param2);
8383

84+
jobjectArray _duckdb_jdbc_cast_result_to_strings(JNIEnv * env, jclass param0, jobject param1, jobject param2, jlong param3);
85+
86+
JNIEXPORT jobjectArray JNICALL Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1cast_1result_1to_1strings(JNIEnv * env, jclass param0, jobject param1, jobject param2, jlong param3);
87+
8488
jint _duckdb_jdbc_fetch_size(JNIEnv * env, jclass param0);
8589

8690
JNIEXPORT jint JNICALL Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1fetch_1size(JNIEnv * env, jclass param0);

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ final class DuckDBNative {
109109

110110
static native DuckDBVector[] duckdb_jdbc_fetch(ByteBuffer res_ref, ByteBuffer conn_ref) throws SQLException;
111111

112+
static native String[] duckdb_jdbc_cast_result_to_strings(ByteBuffer res_ref, ByteBuffer conn_ref, long col_idx)
113+
throws SQLException;
114+
112115
static native int duckdb_jdbc_fetch_size();
113116

114117
static native long duckdb_jdbc_arrow_stream(ByteBuffer res_ref, long batch_size);

src/main/java/org/duckdb/DuckDBResultSet.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,39 @@ public String getString(int columnIndex) throws SQLException {
217217
if (res == null) {
218218
return null;
219219
} else {
220-
return res.toString();
220+
DuckDBColumnType sqlType = meta.column_types[columnIndex - 1];
221+
switch (sqlType) {
222+
case BLOB:
223+
case LIST:
224+
case STRUCT:
225+
case MAP:
226+
case ARRAY:
227+
case UNKNOWN:
228+
case UNION: {
229+
DuckDBVector vec = currentChunk[columnIndex - 1];
230+
if (vec.string_data == null) {
231+
conn.connRefLock.lock();
232+
try {
233+
conn.checkOpen();
234+
resultRefLock.lock();
235+
try {
236+
checkOpen();
237+
if (vec.string_data == null) {
238+
vec.string_data = DuckDBNative.duckdb_jdbc_cast_result_to_strings(
239+
resultRef, conn.connRef, columnIndex - 1);
240+
}
241+
} finally {
242+
resultRefLock.unlock();
243+
}
244+
} finally {
245+
conn.connRefLock.unlock();
246+
}
247+
}
248+
return vec.string_data[chunkIdx - 1];
249+
}
250+
default:
251+
return res.toString();
252+
}
221253
}
222254
}
223255

src/main/java/org/duckdb/DuckDBVector.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ class DuckDBVector {
4141
.toFormatter())
4242
.toFormatter();
4343

44+
private final DuckDBColumnTypeMetaData meta;
45+
protected final DuckDBColumnType duckdb_type;
46+
final int length;
47+
private final boolean[] nullmask;
48+
private ByteBuffer constlen_data = null;
49+
private Object[] varlen_data = null;
50+
String[] string_data = null;
51+
4452
DuckDBVector(String duckdb_type, int length, boolean[] nullmask) {
4553
super();
4654
this.duckdb_type = DuckDBResultSetMetaData.TypeNameToType(duckdb_type);
@@ -50,12 +58,6 @@ class DuckDBVector {
5058
this.length = length;
5159
this.nullmask = nullmask;
5260
}
53-
private final DuckDBColumnTypeMetaData meta;
54-
protected final DuckDBColumnType duckdb_type;
55-
final int length;
56-
private final boolean[] nullmask;
57-
private ByteBuffer constlen_data = null;
58-
private Object[] varlen_data = null;
5961

6062
Object getObject(int idx) throws SQLException {
6163
if (check_and_null(idx)) {

src/test/java/org/duckdb/TestResults.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,4 +256,45 @@ public static void test_stream_multiple_open_results() throws Exception {
256256
}
257257
}
258258
}
259+
260+
public static void test_results_strings_cast() throws Exception {
261+
try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement()) {
262+
263+
// BLOB
264+
try (ResultSet rs = stmt.executeQuery("SELECT 'foo'::BLOB")) {
265+
rs.next();
266+
assertEquals(rs.getString(1), "foo");
267+
}
268+
269+
// LIST
270+
try (ResultSet rs = stmt.executeQuery("SELECT ['foo', 'bar', 'baz']")) {
271+
rs.next();
272+
assertEquals(rs.getString(1), "[foo, bar, baz]");
273+
}
274+
275+
// STRUCT
276+
try (ResultSet rs = stmt.executeQuery("SELECT struct_pack(s1 := 'foo', s2 := 42)")) {
277+
rs.next();
278+
assertEquals(rs.getString(1), "{'s1': foo, 's2': 42}");
279+
}
280+
281+
// MAP
282+
try (ResultSet rs = stmt.executeQuery("SELECT MAP{'foo': 42}")) {
283+
rs.next();
284+
assertEquals(rs.getString(1), "{foo=42}");
285+
}
286+
287+
// UNION
288+
try (ResultSet rs = stmt.executeQuery("SELECT union_value(foo := 42)")) {
289+
rs.next();
290+
assertEquals(rs.getString(1), "42");
291+
}
292+
293+
// ARRAY
294+
try (ResultSet rs = stmt.executeQuery("SELECT ARRAY['foo', 'bar', 'baz']")) {
295+
rs.next();
296+
assertEquals(rs.getString(1), "[foo, bar, baz]");
297+
}
298+
}
299+
}
259300
}

0 commit comments

Comments
 (0)