From b9a5e64f850e6587094435d0a73466ef98876b57 Mon Sep 17 00:00:00 2001 From: Alex Kasko Date: Fri, 5 Jun 2026 19:55:36 +0100 Subject: [PATCH] Fix non-flat vector with nested VARIANT It appeared that with nested `VARIANT`/`JSON` a non-flat (constant) vector can be surfaced during conversion of a result set to Java types. This PR fixes that code path flattening the vector before processing. This is more like a band-aid - for long-term the support of `VARIANT` needs to be re-implemented on top of `DuckDBDataChunkReader` (see #682). Fixes: #692 --- src/jni/duckdb_java.cpp | 7 +++- src/test/java/org/duckdb/TestVariant.java | 42 +++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/jni/duckdb_java.cpp b/src/jni/duckdb_java.cpp index 43c96f887..9263aa25f 100644 --- a/src/jni/duckdb_java.cpp +++ b/src/jni/duckdb_java.cpp @@ -530,6 +530,9 @@ static jobject make_vec_data_buf(JNIEnv *env, Vector &vec, idx_t row_count) { } jobject ProcessVector(JNIEnv *env, Connection *conn_ref, Vector &vec, idx_t row_count) { + if (vec.GetVectorType() != VectorType::FLAT_VECTOR) { + vec.Flatten(row_count); + } auto type_str = env->NewStringUTF(type_to_jduckdb_type(vec.GetType()).c_str()); // construct nullmask auto null_array = env->NewBooleanArray(row_count); @@ -736,7 +739,6 @@ jobject ProcessVector(JNIEnv *env, Connection *conn_ref, Vector &vec, idx_t row_ continue; } Vector variant_vec(variant_val); - variant_vec.Flatten(1); jobject variant_j_vec = ProcessVector(env, conn_ref, variant_vec, 1); env->CallVoidMethod(variant_j_vec, J_DuckVector_retainConstlenData); env->SetObjectArrayElement(varlen_data, row_idx, variant_j_vec); @@ -747,6 +749,9 @@ jobject ProcessVector(JNIEnv *env, Connection *conn_ref, Vector &vec, idx_t row_ Vector string_vec(LogicalType::VARCHAR); VectorOperations::Cast(*conn_ref->context, vec, string_vec, row_count); vec.ReferenceAndSetType(string_vec); + if (vec.GetVectorType() != VectorType::FLAT_VECTOR) { + vec.Flatten(row_count); + } // fall through on purpose } case LogicalTypeId::VARCHAR: diff --git a/src/test/java/org/duckdb/TestVariant.java b/src/test/java/org/duckdb/TestVariant.java index 052802eb1..7cae01623 100644 --- a/src/test/java/org/duckdb/TestVariant.java +++ b/src/test/java/org/duckdb/TestVariant.java @@ -268,4 +268,46 @@ public static void test_variant_struct_with_variant() throws Exception { } } } + + public static void test_variant_nested_struct_json() throws Exception { + try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE IF NOT EXISTS events (\n" + + " globalSequence BIGINT,\n" + + " data VARIANT\n" + + " )"); + + String testData = "{\n" + + " \"id\": \"72869b95-ab13-4405-9371-546b6dcc443e\",\n" + + " \"isSimulation\": false,\n" + + " \"workOrderSchedules\": [\n" + + " {\n" + + " \"workOrderId\": \"e10c064f-6280-41f7-be75-b0075234754b\",\n" + + " \"scheduledStart\": \"2026-06-02T22:56:00\",\n" + + " \"scheduledEnd\": \"2026-06-03T05:01:00\",\n" + + " \"scheduledResourceIds\": [\n" + + " \"a49f8503-f2db-43aa-9d7c-711fc78d8dc9\"\n" + + " ],\n" + + " \"batchId\": null,\n" + + " \"batchSequence\": null\n" + + " }\n" + + " ],\n" + + " \"simulationResultType\": null\n" + + " }"; + + try (PreparedStatement preparedStatement = + conn.prepareStatement("INSERT INTO events (globalSequence, data) VALUES (?, json(?)::VARIANT)")) { + preparedStatement.setLong(1, 100); + preparedStatement.setString(2, testData); + preparedStatement.execute(); + } + try (ResultSet rs = stmt.executeQuery("SELECT globalSequence, data FROM events")) { + assertTrue(rs.next()); + assertEquals(rs.getLong(1), 100L); + assertEquals( + rs.getString(2), + "{id=72869b95-ab13-4405-9371-546b6dcc443e, isSimulation=false, workOrderSchedules=[{workOrderId=e10c064f-6280-41f7-be75-b0075234754b, scheduledStart=2026-06-02T22:56:00, scheduledEnd=2026-06-03T05:01:00, scheduledResourceIds=[a49f8503-f2db-43aa-9d7c-711fc78d8dc9], batchId=null, batchSequence=null}], simulationResultType=null}"); + assertFalse(rs.next()); + } + } + } }