From a2ac08a3a400e2466ee8d582e2dbf75d83d8d5e4 Mon Sep 17 00:00:00 2001 From: Elliana May Date: Tue, 30 Apr 2024 22:51:13 +0800 Subject: [PATCH 01/15] naive struct writing --- src/jni/duckdb_java.cpp | 58 +++++++++++++++++-- .../java/org/duckdb/DuckDBConnection.java | 2 +- src/test/java/org/duckdb/TestDuckDBJDBC.java | 24 ++++++++ 3 files changed, 79 insertions(+), 5 deletions(-) diff --git a/src/jni/duckdb_java.cpp b/src/jni/duckdb_java.cpp index ae6a829f..8efa6f20 100644 --- a/src/jni/duckdb_java.cpp +++ b/src/jni/duckdb_java.cpp @@ -68,6 +68,13 @@ static jfieldID J_DuckVector_varlen; static jclass J_DuckArray; static jmethodID J_DuckArray_init; +static jclass J_Struct; +static jmethodID J_Struct_getSQLTypeName; +static jmethodID J_Struct_getAttributes; + +static jclass J_Object; +static jmethodID J_Object_toString; + static jclass J_DuckStruct; static jmethodID J_DuckStruct_init; @@ -207,13 +214,17 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { D_ASSERT(J_DuckArray_init); env->DeleteLocalRef(tmpLocalRef); - tmpLocalRef = env->FindClass("org/duckdb/DuckDBStruct"); - D_ASSERT(tmpLocalRef); - J_DuckStruct = (jclass)env->NewGlobalRef(tmpLocalRef); + J_DuckStruct = GetClassRef(env, "org/duckdb/DuckDBStruct"); J_DuckStruct_init = env->GetMethodID(J_DuckStruct, "", "([Ljava/lang/String;[Lorg/duckdb/DuckDBVector;ILjava/lang/String;)V"); D_ASSERT(J_DuckStruct_init); - env->DeleteLocalRef(tmpLocalRef); + + J_Struct = GetClassRef(env, "java/sql/Struct"); + J_Struct_getSQLTypeName = env->GetMethodID(J_Struct, "getSQLTypeName", "()Ljava/lang/String;"); + J_Struct_getAttributes = env->GetMethodID(J_Struct, "getAttributes", "()[Ljava/lang/Object;"); + + J_Object = GetClassRef(env, "java/lang/Object"); + J_Object_toString = env->GetMethodID(J_Object, "toString", "()Ljava/lang/String;"); tmpLocalRef = env->FindClass("java/util/Map$Entry"); J_Entry_getKey = env->GetMethodID(tmpLocalRef, "getKey", "()Ljava/lang/Object;"); @@ -557,11 +568,27 @@ struct ResultHolder { duckdb::unique_ptr chunk; }; +static string GetString(JNIEnv *env, jobject key) { + auto str = (jstring)env->CallObjectMethod(key, J_Object_toString); + D_ASSERT(str); + return jstring_to_string(env, str); +} + jobject _duckdb_jdbc_execute(JNIEnv *env, jclass, jobject stmt_ref_buf, jobjectArray params) { auto stmt_ref = (StatementHolder *)env->GetDirectBufferAddress(stmt_ref_buf); if (!stmt_ref) { throw InvalidInputException("Invalid statement"); } + + try { + return execute(env, stmt_ref, params); + } catch (const exception &e) { + env->ThrowNew(J_SQLException, e.what()); + return nullptr; + } +} + +static jobject execute(JNIEnv *env, StatementHolder *stmt_ref, jobjectArray params) { auto res_ref = make_uniq(); duckdb::vector duckdb_params; @@ -625,6 +652,29 @@ jobject _duckdb_jdbc_execute(JNIEnv *env, jclass, jobject stmt_ref_buf, jobjectA auto most_significant = (jlong)env->CallObjectMethod(param, J_UUID_getMostSignificantBits); auto least_significant = (jlong)env->CallObjectMethod(param, J_UUID_getLeastSignificantBits); duckdb_params.push_back(Value::UUID(hugeint_t(most_significant, least_significant))); + } else if (env->IsInstanceOf(param, J_Struct)) { + auto typeName = jstring_to_string(env, (jstring)env->CallObjectMethod(param, J_Struct_getSQLTypeName)); + + auto &context = stmt_ref->stmt->context; + LogicalType type; + context->RunFunctionInTransaction([&]() { type = TransformStringToLogicalType(typeName, *context); }); + + auto jvalues = (jobjectArray)env->CallObjectMethod(param, J_Struct_getAttributes); + + int size = env->GetArrayLength(jvalues); + + child_list_t values; + + for (int i = 0; i < size; i++) { + auto name = StructType::GetChildName(type, i); + + auto value = env->GetObjectArrayElement(jvalues, i); + auto value_string = env->CallObjectMethod(value, J_Object_toString); + + values.emplace_back(name, Value(jstring_to_string(env, (jstring)value_string))); + } + + duckdb_params.push_back(Value::STRUCT(std::move(values))); } else { throw InvalidInputException("Unsupported parameter type"); } diff --git a/src/main/java/org/duckdb/DuckDBConnection.java b/src/main/java/org/duckdb/DuckDBConnection.java index 1a5b0e53..c82b2500 100644 --- a/src/main/java/org/duckdb/DuckDBConnection.java +++ b/src/main/java/org/duckdb/DuckDBConnection.java @@ -331,7 +331,7 @@ public Array createArrayOf(String typeName, Object[] elements) throws SQLExcepti } public Struct createStruct(String typeName, Object[] attributes) throws SQLException { - throw new SQLFeatureNotSupportedException("createStruct"); + return new MyStruct(typeName, attributes); } public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { diff --git a/src/test/java/org/duckdb/TestDuckDBJDBC.java b/src/test/java/org/duckdb/TestDuckDBJDBC.java index 1d2425e8..93894718 100644 --- a/src/test/java/org/duckdb/TestDuckDBJDBC.java +++ b/src/test/java/org/duckdb/TestDuckDBJDBC.java @@ -3432,6 +3432,30 @@ public static void test_structs() throws Exception { Struct struct = (Struct) resultSet.getObject(1); assertEquals(toJavaObject(struct), mapOf("a", 1)); assertEquals(struct.getSQLTypeName(), "STRUCT(a INTEGER)"); + + String definition = "STRUCT(i INTEGER, j INTEGER)"; + String typeName = "POINT"; + try (PreparedStatement stmt = + connection.prepareStatement("CREATE TYPE " + typeName + " AS " + definition)) { + stmt.execute(); + } + + testStruct(connection, connection.createStruct(definition, new Object[] {1, 2})); + testStruct(connection, connection.createStruct(typeName, new Object[] {1, 2})); + } + } + + private static void testStruct(Connection connection, Struct struct) throws SQLException, Exception { + try (PreparedStatement stmt = connection.prepareStatement("SELECT ?")) { + stmt.setObject(1, struct); + + try (ResultSet rs = stmt.executeQuery()) { + assertTrue(rs.next()); + + Struct result = (Struct) rs.getObject(1); + + assertEquals(Arrays.asList("1", "2"), Arrays.asList(result.getAttributes())); + } } } From e15aa0f22585098e83d219607d4238ab1f457325 Mon Sep 17 00:00:00 2001 From: Elliana May Date: Tue, 30 Apr 2024 22:52:36 +0800 Subject: [PATCH 02/15] wip array writing support --- src/jni/duckdb_java.cpp | 26 +++++++++++++++++++ .../java/org/duckdb/DuckDBConnection.java | 2 +- src/test/java/org/duckdb/TestDuckDBJDBC.java | 10 +++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/jni/duckdb_java.cpp b/src/jni/duckdb_java.cpp index 8efa6f20..4b1bc3c0 100644 --- a/src/jni/duckdb_java.cpp +++ b/src/jni/duckdb_java.cpp @@ -72,6 +72,10 @@ static jclass J_Struct; static jmethodID J_Struct_getSQLTypeName; static jmethodID J_Struct_getAttributes; +static jclass J_Array; +static jmethodID J_Array_getBaseTypeName; +static jmethodID J_Array_getArray; + static jclass J_Object; static jmethodID J_Object_toString; @@ -223,6 +227,10 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { J_Struct_getSQLTypeName = env->GetMethodID(J_Struct, "getSQLTypeName", "()Ljava/lang/String;"); J_Struct_getAttributes = env->GetMethodID(J_Struct, "getAttributes", "()[Ljava/lang/Object;"); + J_Array = GetClassRef(env, "java/sql/Array"); + J_Array_getArray = env->GetMethodID(J_Array, "getArray", "()Ljava/lang/Object;"); + J_Array_getBaseTypeName = env->GetMethodID(J_Array, "getBaseTypeName", "()Ljava/lang/String;"); + J_Object = GetClassRef(env, "java/lang/Object"); J_Object_toString = env->GetMethodID(J_Object, "toString", "()Ljava/lang/String;"); @@ -675,6 +683,24 @@ static jobject execute(JNIEnv *env, StatementHolder *stmt_ref, jobjectArray para } duckdb_params.push_back(Value::STRUCT(std::move(values))); + } else if (env->IsInstanceOf(param, J_Array)) { + auto typeName = jstring_to_string(env, (jstring)env->CallObjectMethod(param, J_Array_getBaseTypeName)); + auto jvalues = (jobjectArray)env->CallObjectMethod(param, J_Array_getArray); + int size = env->GetArrayLength(jvalues); + + auto &context = stmt_ref->stmt->context; + LogicalType type; + context->RunFunctionInTransaction([&]() { type = TransformStringToLogicalType(typeName, *context); }); + + duckdb::vector values; + for (int i = 0; i < size; i++) { + auto value = env->GetObjectArrayElement(jvalues, i); + auto value_string = env->CallObjectMethod(value, J_Object_toString); + values.emplace_back(jstring_to_string(env, (jstring)value_string)); + } + + duckdb_params.push_back(Value::LIST(type, values)); + } else { throw InvalidInputException("Unsupported parameter type"); } diff --git a/src/main/java/org/duckdb/DuckDBConnection.java b/src/main/java/org/duckdb/DuckDBConnection.java index c82b2500..bc9e8135 100644 --- a/src/main/java/org/duckdb/DuckDBConnection.java +++ b/src/main/java/org/duckdb/DuckDBConnection.java @@ -327,7 +327,7 @@ public Properties getClientInfo() throws SQLException { } public Array createArrayOf(String typeName, Object[] elements) throws SQLException { - throw new SQLFeatureNotSupportedException("createArrayOf"); + return new MyArray(typeName, elements); } public Struct createStruct(String typeName, Object[] attributes) throws SQLException { diff --git a/src/test/java/org/duckdb/TestDuckDBJDBC.java b/src/test/java/org/duckdb/TestDuckDBJDBC.java index 93894718..fbfcd73b 100644 --- a/src/test/java/org/duckdb/TestDuckDBJDBC.java +++ b/src/test/java/org/duckdb/TestDuckDBJDBC.java @@ -3507,6 +3507,16 @@ public static void test_list() throws Exception { assertTrue(rs.next()); assertEquals(arrayToList(rs.getArray(1)), singletonList(new BigDecimal("0.000"))); } + try (PreparedStatement stmt = connection.prepareStatement("select ?")) { + Array array = connection.createArrayOf("INTEGER", new Object[] {1}); + + stmt.setObject(1, array); + + try (ResultSet rs = stmt.executeQuery()) { + assertTrue(rs.next()); + assertEquals(singletonList(1), arrayToList(rs.getArray(1))); + } + } } } From 5f77f9fb4f4fe28a9fa25b1d7f2b05c9fc25b7b1 Mon Sep 17 00:00:00 2001 From: Elliana May Date: Tue, 30 Apr 2024 22:53:37 +0800 Subject: [PATCH 03/15] add missing classes --- .../java/org/duckdb/DuckDBConnection.java | 7 +- .../java/org/duckdb/user/DuckDBUserArray.java | 72 +++++++++++++++++++ .../org/duckdb/user/DuckDBUserStruct.java | 30 ++++++++ 3 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/duckdb/user/DuckDBUserArray.java create mode 100644 src/main/java/org/duckdb/user/DuckDBUserStruct.java diff --git a/src/main/java/org/duckdb/DuckDBConnection.java b/src/main/java/org/duckdb/DuckDBConnection.java index bc9e8135..4753c37c 100644 --- a/src/main/java/org/duckdb/DuckDBConnection.java +++ b/src/main/java/org/duckdb/DuckDBConnection.java @@ -1,5 +1,8 @@ package org.duckdb; +import org.duckdb.user.DuckDBUserArray; +import org.duckdb.user.DuckDBUserStruct; + import java.lang.reflect.InvocationTargetException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; @@ -327,11 +330,11 @@ public Properties getClientInfo() throws SQLException { } public Array createArrayOf(String typeName, Object[] elements) throws SQLException { - return new MyArray(typeName, elements); + return new DuckDBUserArray(typeName, elements); } public Struct createStruct(String typeName, Object[] attributes) throws SQLException { - return new MyStruct(typeName, attributes); + return new DuckDBUserStruct(typeName, attributes); } public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { diff --git a/src/main/java/org/duckdb/user/DuckDBUserArray.java b/src/main/java/org/duckdb/user/DuckDBUserArray.java new file mode 100644 index 00000000..8be20c4f --- /dev/null +++ b/src/main/java/org/duckdb/user/DuckDBUserArray.java @@ -0,0 +1,72 @@ +package org.duckdb.user; + +import java.sql.Array; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.Map; + +public class DuckDBUserArray implements Array { + private final String typeName; + private final Object[] elements; + + public DuckDBUserArray(String typeName, Object[] elements) { + this.typeName = typeName; + this.elements = elements; + } + + @Override + public String getBaseTypeName() throws SQLException { + return typeName; + } + + @Override + public int getBaseType() throws SQLException { + throw new SQLFeatureNotSupportedException("getBaseType"); + } + + @Override + public Object getArray() throws SQLException { + return elements; + } + + @Override + public Object getArray(Map> map) throws SQLException { + return getArray(); + } + + @Override + public Object getArray(long index, int count) throws SQLException { + throw new SQLFeatureNotSupportedException("getArray"); + } + + @Override + public Object getArray(long index, int count, Map> map) throws SQLException { + throw new SQLFeatureNotSupportedException("getArray"); + } + + @Override + public ResultSet getResultSet() throws SQLException { + throw new SQLFeatureNotSupportedException("getResultSet"); + } + + @Override + public ResultSet getResultSet(Map> map) throws SQLException { + throw new SQLFeatureNotSupportedException("getResultSet"); + } + + @Override + public ResultSet getResultSet(long index, int count) throws SQLException { + throw new SQLFeatureNotSupportedException("getResultSet"); + } + + @Override + public ResultSet getResultSet(long index, int count, Map> map) throws SQLException { + throw new SQLFeatureNotSupportedException("getResultSet"); + } + + @Override + public void free() throws SQLException { + // no-op + } +} diff --git a/src/main/java/org/duckdb/user/DuckDBUserStruct.java b/src/main/java/org/duckdb/user/DuckDBUserStruct.java new file mode 100644 index 00000000..379a24ae --- /dev/null +++ b/src/main/java/org/duckdb/user/DuckDBUserStruct.java @@ -0,0 +1,30 @@ +package org.duckdb.user; + +import java.sql.SQLException; +import java.sql.Struct; +import java.util.Map; + +public class DuckDBUserStruct implements Struct { + private final String typeName; + private final Object[] attributes; + + public DuckDBUserStruct(String typeName, Object[] attributes) { + this.typeName = typeName; + this.attributes = attributes; + } + + @Override + public String getSQLTypeName() throws SQLException { + return typeName; + } + + @Override + public Object[] getAttributes() throws SQLException { + return attributes; + } + + @Override + public Object[] getAttributes(Map> map) throws SQLException { + return getAttributes(); + } +} From 265e750672f3e7f25c6fab7fcca32b8deb7ac7c4 Mon Sep 17 00:00:00 2001 From: Elliana May Date: Tue, 30 Apr 2024 22:55:10 +0800 Subject: [PATCH 04/15] add DuckDBMap class --- src/jni/duckdb_java.cpp | 52 +++++++++++++------ .../java/org/duckdb/DuckDBConnection.java | 5 ++ src/main/java/org/duckdb/user/DuckDBMap.java | 17 ++++++ src/test/java/org/duckdb/TestDuckDBJDBC.java | 18 +++++++ 4 files changed, 77 insertions(+), 15 deletions(-) create mode 100644 src/main/java/org/duckdb/user/DuckDBMap.java diff --git a/src/jni/duckdb_java.cpp b/src/jni/duckdb_java.cpp index 4b1bc3c0..694c6079 100644 --- a/src/jni/duckdb_java.cpp +++ b/src/jni/duckdb_java.cpp @@ -84,6 +84,9 @@ static jmethodID J_DuckStruct_init; static jclass J_ByteBuffer; +static jclass J_DuckMap; +static jmethodID J_DuckMap_getSQLTypeName; + static jmethodID J_Map_entrySet; static jmethodID J_Set_iterator; static jmethodID J_Iterator_hasNext; @@ -192,6 +195,11 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { J_ByteArray = (jclass)env->NewGlobalRef(tmpLocalRef); env->DeleteLocalRef(tmpLocalRef); + J_DuckMap = GetClassRef(env, "org/duckdb/user/DuckDBMap"); + D_ASSERT(J_DuckMap); + J_DuckMap_getSQLTypeName = env->GetMethodID(J_DuckMap, "getSQLTypeName", "()Ljava/lang/String;"); + D_ASSERT(J_DuckMap_getSQLTypeName); + tmpLocalRef = env->FindClass("java/util/Map"); J_Map_entrySet = env->GetMethodID(tmpLocalRef, "entrySet", "()Ljava/util/Set;"); env->DeleteLocalRef(tmpLocalRef); @@ -588,15 +596,6 @@ jobject _duckdb_jdbc_execute(JNIEnv *env, jclass, jobject stmt_ref_buf, jobjectA throw InvalidInputException("Invalid statement"); } - try { - return execute(env, stmt_ref, params); - } catch (const exception &e) { - env->ThrowNew(J_SQLException, e.what()); - return nullptr; - } -} - -static jobject execute(JNIEnv *env, StatementHolder *stmt_ref, jobjectArray params) { auto res_ref = make_uniq(); duckdb::vector duckdb_params; @@ -605,6 +604,8 @@ static jobject execute(JNIEnv *env, StatementHolder *stmt_ref, jobjectArray para throw InvalidInputException("Parameter count mismatch"); } + auto &context = stmt_ref->stmt->context; + if (param_len > 0) { for (idx_t i = 0; i < param_len; i++) { auto param = env->GetObjectArrayElement(params, i); @@ -660,10 +661,31 @@ static jobject execute(JNIEnv *env, StatementHolder *stmt_ref, jobjectArray para auto most_significant = (jlong)env->CallObjectMethod(param, J_UUID_getMostSignificantBits); auto least_significant = (jlong)env->CallObjectMethod(param, J_UUID_getLeastSignificantBits); duckdb_params.push_back(Value::UUID(hugeint_t(most_significant, least_significant))); + } else if (env->IsInstanceOf(param, J_DuckMap)) { + auto typeName = jstring_to_string(env, (jstring)env->CallObjectMethod(param, J_DuckMap_getSQLTypeName)); + + LogicalType type; + context->RunFunctionInTransaction([&]() { type = TransformStringToLogicalType(typeName, *context); }); + + auto entrySet = env->CallObjectMethod(param, J_Map_entrySet); + auto iterator = env->CallObjectMethod(entrySet, J_Set_iterator); + duckdb::vector entries; + while (env->CallObjectMethod(iterator, J_Iterator_hasNext)) { + auto entry = env->CallObjectMethod(iterator, J_Iterator_next); + + auto key = env->CallObjectMethod(entry, J_Entry_getKey); + auto value = env->CallObjectMethod(entry, J_Entry_getValue); + D_ASSERT(key); + D_ASSERT(value); + + entries.push_back(Value::STRUCT({{"key", GetString(env, key)}, {"value", GetString(env, value)}})); + } + + duckdb_params.push_back(Value::MAP(ListType::GetChildType(type), entries)); + } else if (env->IsInstanceOf(param, J_Struct)) { auto typeName = jstring_to_string(env, (jstring)env->CallObjectMethod(param, J_Struct_getSQLTypeName)); - auto &context = stmt_ref->stmt->context; LogicalType type; context->RunFunctionInTransaction([&]() { type = TransformStringToLogicalType(typeName, *context); }); @@ -677,9 +699,9 @@ static jobject execute(JNIEnv *env, StatementHolder *stmt_ref, jobjectArray para auto name = StructType::GetChildName(type, i); auto value = env->GetObjectArrayElement(jvalues, i); - auto value_string = env->CallObjectMethod(value, J_Object_toString); - values.emplace_back(name, Value(jstring_to_string(env, (jstring)value_string))); + // FIXME: use real type + values.emplace_back(name, GetString(env, value)); } duckdb_params.push_back(Value::STRUCT(std::move(values))); @@ -688,15 +710,15 @@ static jobject execute(JNIEnv *env, StatementHolder *stmt_ref, jobjectArray para auto jvalues = (jobjectArray)env->CallObjectMethod(param, J_Array_getArray); int size = env->GetArrayLength(jvalues); - auto &context = stmt_ref->stmt->context; LogicalType type; context->RunFunctionInTransaction([&]() { type = TransformStringToLogicalType(typeName, *context); }); duckdb::vector values; for (int i = 0; i < size; i++) { auto value = env->GetObjectArrayElement(jvalues, i); - auto value_string = env->CallObjectMethod(value, J_Object_toString); - values.emplace_back(jstring_to_string(env, (jstring)value_string)); + + // FIXME: use real type + values.emplace_back(GetString(env, value)); } duckdb_params.push_back(Value::LIST(type, values)); diff --git a/src/main/java/org/duckdb/DuckDBConnection.java b/src/main/java/org/duckdb/DuckDBConnection.java index 4753c37c..e058bdf5 100644 --- a/src/main/java/org/duckdb/DuckDBConnection.java +++ b/src/main/java/org/duckdb/DuckDBConnection.java @@ -1,5 +1,6 @@ package org.duckdb; +import org.duckdb.user.DuckDBMap; import org.duckdb.user.DuckDBUserArray; import org.duckdb.user.DuckDBUserStruct; @@ -337,6 +338,10 @@ public Struct createStruct(String typeName, Object[] attributes) throws SQLExcep return new DuckDBUserStruct(typeName, attributes); } + public Map createMap(String typeName, Map map) { + return new DuckDBMap<>(typeName, map); + } + public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { throw new SQLFeatureNotSupportedException("setNetworkTimeout"); } diff --git a/src/main/java/org/duckdb/user/DuckDBMap.java b/src/main/java/org/duckdb/user/DuckDBMap.java new file mode 100644 index 00000000..b294f817 --- /dev/null +++ b/src/main/java/org/duckdb/user/DuckDBMap.java @@ -0,0 +1,17 @@ +package org.duckdb.user; + +import java.util.HashMap; +import java.util.Map; + +public class DuckDBMap extends HashMap { + private final String typeName; + + public DuckDBMap(String typeName, Map map) { + super(map); + this.typeName = typeName; + } + + public String getSQLTypeName() { + return typeName; + } +} diff --git a/src/test/java/org/duckdb/TestDuckDBJDBC.java b/src/test/java/org/duckdb/TestDuckDBJDBC.java index fbfcd73b..4b0a947d 100644 --- a/src/test/java/org/duckdb/TestDuckDBJDBC.java +++ b/src/test/java/org/duckdb/TestDuckDBJDBC.java @@ -3459,6 +3459,24 @@ private static void testStruct(Connection connection, Struct struct) throws SQLE } } + public static void test_write_map() throws Exception { + try (DuckDBConnection conn = DriverManager.getConnection("jdbc:duckdb:").unwrap(DuckDBConnection.class)) { + try (Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE test (thing MAP(string, integer));"); + } + Map map = mapOf("hello", 42); + try (PreparedStatement stmt = conn.prepareStatement("INSERT INTO test VALUES (?)")) { + stmt.setObject(1, conn.createMap("MAP(string, integer)", map)); + assertEquals(stmt.executeUpdate(), 1); + } + try (PreparedStatement stmt = conn.prepareStatement("FROM test"); ResultSet rs = stmt.executeQuery()) { + while (rs.next()) { + assertEquals(rs.getObject(1), map); + } + } + } + } + public static void test_union() throws Exception { try (Connection connection = DriverManager.getConnection(JDBC_URL); Statement statement = connection.createStatement()) { From 01935de25b452d84ef0007263eefe5b6781517ff Mon Sep 17 00:00:00 2001 From: Elliana May Date: Tue, 30 Apr 2024 22:59:04 +0800 Subject: [PATCH 05/15] add ToValue method to allow reuse of jobject -> Value logic --- src/jni/duckdb_java.cpp | 228 +++++++++++++++++++--------------------- 1 file changed, 107 insertions(+), 121 deletions(-) diff --git a/src/jni/duckdb_java.cpp b/src/jni/duckdb_java.cpp index 694c6079..6d976b0b 100644 --- a/src/jni/duckdb_java.cpp +++ b/src/jni/duckdb_java.cpp @@ -584,10 +584,112 @@ struct ResultHolder { duckdb::unique_ptr chunk; }; -static string GetString(JNIEnv *env, jobject key) { - auto str = (jstring)env->CallObjectMethod(key, J_Object_toString); - D_ASSERT(str); - return jstring_to_string(env, str); +Value ToValue(JNIEnv *env, jobject param, shared_ptr context) { + if (param == nullptr) { + return (Value()); + } else if (env->IsInstanceOf(param, J_Bool)) { + return (Value::BOOLEAN(env->CallBooleanMethod(param, J_Bool_booleanValue))); + } else if (env->IsInstanceOf(param, J_Byte)) { + return (Value::TINYINT(env->CallByteMethod(param, J_Byte_byteValue))); + } else if (env->IsInstanceOf(param, J_Short)) { + return (Value::SMALLINT(env->CallShortMethod(param, J_Short_shortValue))); + } else if (env->IsInstanceOf(param, J_Int)) { + return (Value::INTEGER(env->CallIntMethod(param, J_Int_intValue))); + } else if (env->IsInstanceOf(param, J_Long)) { + return (Value::BIGINT(env->CallLongMethod(param, J_Long_longValue))); + } else if (env->IsInstanceOf(param, J_TimestampTZ)) { // Check for subclass before superclass! + return ( + Value::TIMESTAMPTZ((timestamp_t)env->CallLongMethod(param, J_TimestampTZ_getMicrosEpoch))); + } else if (env->IsInstanceOf(param, J_DuckDBDate)) { + return ( + Value::DATE((date_t)env->CallLongMethod(param, J_DuckDBDate_getDaysSinceEpoch))); + + } else if (env->IsInstanceOf(param, J_DuckDBTime)) { + return (Value::TIME((dtime_t)env->CallLongMethod(param, J_Timestamp_getMicrosEpoch))); + } else if (env->IsInstanceOf(param, J_Timestamp)) { + return ( + Value::TIMESTAMP((timestamp_t)env->CallLongMethod(param, J_Timestamp_getMicrosEpoch))); + } else if (env->IsInstanceOf(param, J_Float)) { + return (Value::FLOAT(env->CallFloatMethod(param, J_Float_floatValue))); + } else if (env->IsInstanceOf(param, J_Double)) { + return (Value::DOUBLE(env->CallDoubleMethod(param, J_Double_doubleValue))); + } else if (env->IsInstanceOf(param, J_Decimal)) { + Value val = create_value_from_bigdecimal(env, param); + return (val); + } else if (env->IsInstanceOf(param, J_String)) { + auto param_string = jstring_to_string(env, (jstring)param); + return (Value(param_string)); + } else if (env->IsInstanceOf(param, J_ByteArray)) { + return (Value::BLOB_RAW(byte_array_to_string(env, (jbyteArray)param))); + } else if (env->IsInstanceOf(param, J_UUID)) { + auto most_significant = (jlong)env->CallObjectMethod(param, J_UUID_getMostSignificantBits); + auto least_significant = (jlong)env->CallObjectMethod(param, J_UUID_getLeastSignificantBits); + return (Value::UUID(hugeint_t(most_significant, least_significant))); + } else if (env->IsInstanceOf(param, J_DuckMap)) { + auto typeName = jstring_to_string(env, (jstring)env->CallObjectMethod(param, J_DuckMap_getSQLTypeName)); + + LogicalType type; + context->RunFunctionInTransaction([&]() { type = TransformStringToLogicalType(typeName, *context); }); + + auto entrySet = env->CallObjectMethod(param, J_Map_entrySet); + auto iterator = env->CallObjectMethod(entrySet, J_Set_iterator); + duckdb::vector entries; + while (env->CallObjectMethod(iterator, J_Iterator_hasNext)) { + auto entry = env->CallObjectMethod(iterator, J_Iterator_next); + + auto key = env->CallObjectMethod(entry, J_Entry_getKey); + auto value = env->CallObjectMethod(entry, J_Entry_getValue); + D_ASSERT(key); + D_ASSERT(value); + + entries.push_back(Value::STRUCT({{"key", GetString(env, key)}, {"value", GetString(env, value)}})); + } + + return (Value::MAP(ListType::GetChildType(type), entries)); + + } else if (env->IsInstanceOf(param, J_Struct)) { + auto typeName = jstring_to_string(env, (jstring)env->CallObjectMethod(param, J_Struct_getSQLTypeName)); + + LogicalType type; + context->RunFunctionInTransaction([&]() { type = TransformStringToLogicalType(typeName, *context); }); + + auto jvalues = (jobjectArray)env->CallObjectMethod(param, J_Struct_getAttributes); + + int size = env->GetArrayLength(jvalues); + + child_list_t values; + + for (int i = 0; i < size; i++) { + auto name = StructType::GetChildName(type, i); + + auto value = env->GetObjectArrayElement(jvalues, i); + + // FIXME: use real type + values.emplace_back(name, GetString(env, value)); + } + + return (Value::STRUCT(std::move(values))); + } else if (env->IsInstanceOf(param, J_Array)) { + auto typeName = jstring_to_string(env, (jstring)env->CallObjectMethod(param, J_Array_getBaseTypeName)); + auto jvalues = (jobjectArray)env->CallObjectMethod(param, J_Array_getArray); + int size = env->GetArrayLength(jvalues); + + LogicalType type; + context->RunFunctionInTransaction([&]() { type = TransformStringToLogicalType(typeName, *context); }); + + duckdb::vector values; + for (int i = 0; i < size; i++) { + auto value = env->GetObjectArrayElement(jvalues, i); + + // FIXME: use real type + values.emplace_back(GetString(env, value)); + } + + return (Value::LIST(type, values)); + + } else { + throw InvalidInputException("Unsupported parameter type"); + } } jobject _duckdb_jdbc_execute(JNIEnv *env, jclass, jobject stmt_ref_buf, jobjectArray params) { @@ -609,123 +711,7 @@ jobject _duckdb_jdbc_execute(JNIEnv *env, jclass, jobject stmt_ref_buf, jobjectA if (param_len > 0) { for (idx_t i = 0; i < param_len; i++) { auto param = env->GetObjectArrayElement(params, i); - if (param == nullptr) { - duckdb_params.push_back(Value()); - continue; - } else if (env->IsInstanceOf(param, J_Bool)) { - duckdb_params.push_back(Value::BOOLEAN(env->CallBooleanMethod(param, J_Bool_booleanValue))); - continue; - } else if (env->IsInstanceOf(param, J_Byte)) { - duckdb_params.push_back(Value::TINYINT(env->CallByteMethod(param, J_Byte_byteValue))); - continue; - } else if (env->IsInstanceOf(param, J_Short)) { - duckdb_params.push_back(Value::SMALLINT(env->CallShortMethod(param, J_Short_shortValue))); - continue; - } else if (env->IsInstanceOf(param, J_Int)) { - duckdb_params.push_back(Value::INTEGER(env->CallIntMethod(param, J_Int_intValue))); - continue; - } else if (env->IsInstanceOf(param, J_Long)) { - duckdb_params.push_back(Value::BIGINT(env->CallLongMethod(param, J_Long_longValue))); - continue; - } else if (env->IsInstanceOf(param, J_TimestampTZ)) { // Check for subclass before superclass! - duckdb_params.push_back( - Value::TIMESTAMPTZ((timestamp_t)env->CallLongMethod(param, J_TimestampTZ_getMicrosEpoch))); - continue; - } else if (env->IsInstanceOf(param, J_DuckDBDate)) { - duckdb_params.push_back( - Value::DATE((date_t)env->CallLongMethod(param, J_DuckDBDate_getDaysSinceEpoch))); - - } else if (env->IsInstanceOf(param, J_DuckDBTime)) { - duckdb_params.push_back(Value::TIME((dtime_t)env->CallLongMethod(param, J_Timestamp_getMicrosEpoch))); - - } else if (env->IsInstanceOf(param, J_Timestamp)) { - duckdb_params.push_back( - Value::TIMESTAMP((timestamp_t)env->CallLongMethod(param, J_Timestamp_getMicrosEpoch))); - continue; - } else if (env->IsInstanceOf(param, J_Float)) { - duckdb_params.push_back(Value::FLOAT(env->CallFloatMethod(param, J_Float_floatValue))); - continue; - } else if (env->IsInstanceOf(param, J_Double)) { - duckdb_params.push_back(Value::DOUBLE(env->CallDoubleMethod(param, J_Double_doubleValue))); - continue; - } else if (env->IsInstanceOf(param, J_Decimal)) { - Value val = create_value_from_bigdecimal(env, param); - duckdb_params.push_back(val); - continue; - } else if (env->IsInstanceOf(param, J_String)) { - auto param_string = jstring_to_string(env, (jstring)param); - duckdb_params.push_back(Value(param_string)); - } else if (env->IsInstanceOf(param, J_ByteArray)) { - duckdb_params.push_back(Value::BLOB_RAW(byte_array_to_string(env, (jbyteArray)param))); - } else if (env->IsInstanceOf(param, J_UUID)) { - auto most_significant = (jlong)env->CallObjectMethod(param, J_UUID_getMostSignificantBits); - auto least_significant = (jlong)env->CallObjectMethod(param, J_UUID_getLeastSignificantBits); - duckdb_params.push_back(Value::UUID(hugeint_t(most_significant, least_significant))); - } else if (env->IsInstanceOf(param, J_DuckMap)) { - auto typeName = jstring_to_string(env, (jstring)env->CallObjectMethod(param, J_DuckMap_getSQLTypeName)); - - LogicalType type; - context->RunFunctionInTransaction([&]() { type = TransformStringToLogicalType(typeName, *context); }); - - auto entrySet = env->CallObjectMethod(param, J_Map_entrySet); - auto iterator = env->CallObjectMethod(entrySet, J_Set_iterator); - duckdb::vector entries; - while (env->CallObjectMethod(iterator, J_Iterator_hasNext)) { - auto entry = env->CallObjectMethod(iterator, J_Iterator_next); - - auto key = env->CallObjectMethod(entry, J_Entry_getKey); - auto value = env->CallObjectMethod(entry, J_Entry_getValue); - D_ASSERT(key); - D_ASSERT(value); - - entries.push_back(Value::STRUCT({{"key", GetString(env, key)}, {"value", GetString(env, value)}})); - } - - duckdb_params.push_back(Value::MAP(ListType::GetChildType(type), entries)); - - } else if (env->IsInstanceOf(param, J_Struct)) { - auto typeName = jstring_to_string(env, (jstring)env->CallObjectMethod(param, J_Struct_getSQLTypeName)); - - LogicalType type; - context->RunFunctionInTransaction([&]() { type = TransformStringToLogicalType(typeName, *context); }); - - auto jvalues = (jobjectArray)env->CallObjectMethod(param, J_Struct_getAttributes); - - int size = env->GetArrayLength(jvalues); - - child_list_t values; - - for (int i = 0; i < size; i++) { - auto name = StructType::GetChildName(type, i); - - auto value = env->GetObjectArrayElement(jvalues, i); - - // FIXME: use real type - values.emplace_back(name, GetString(env, value)); - } - - duckdb_params.push_back(Value::STRUCT(std::move(values))); - } else if (env->IsInstanceOf(param, J_Array)) { - auto typeName = jstring_to_string(env, (jstring)env->CallObjectMethod(param, J_Array_getBaseTypeName)); - auto jvalues = (jobjectArray)env->CallObjectMethod(param, J_Array_getArray); - int size = env->GetArrayLength(jvalues); - - LogicalType type; - context->RunFunctionInTransaction([&]() { type = TransformStringToLogicalType(typeName, *context); }); - - duckdb::vector values; - for (int i = 0; i < size; i++) { - auto value = env->GetObjectArrayElement(jvalues, i); - - // FIXME: use real type - values.emplace_back(GetString(env, value)); - } - - duckdb_params.push_back(Value::LIST(type, values)); - - } else { - throw InvalidInputException("Unsupported parameter type"); - } + duckdb_params.push_back(ToValue(env, param, context)); } } From 211f1142618be5b3a3bf04b06bcfabe572074643 Mon Sep 17 00:00:00 2001 From: Elliana May Date: Tue, 30 Apr 2024 23:08:45 +0800 Subject: [PATCH 06/15] missing import --- src/test/java/org/duckdb/TestDuckDBJDBC.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/org/duckdb/TestDuckDBJDBC.java b/src/test/java/org/duckdb/TestDuckDBJDBC.java index 4b0a947d..d3d9f1f3 100644 --- a/src/test/java/org/duckdb/TestDuckDBJDBC.java +++ b/src/test/java/org/duckdb/TestDuckDBJDBC.java @@ -14,6 +14,7 @@ import java.sql.DatabaseMetaData; import java.sql.Date; import java.sql.Driver; +import java.util.Arrays; import java.util.concurrent.Future; import java.sql.DriverManager; import java.sql.PreparedStatement; From feec76b196f69a5e36ce5190305a13c50af54b1e Mon Sep 17 00:00:00 2001 From: Elliana May Date: Wed, 1 May 2024 00:10:50 +0800 Subject: [PATCH 07/15] add J_Timestamp#valueOf(...) --- src/jni/duckdb_java.cpp | 6 ++++++ .../org/duckdb/DuckDBPreparedStatement.java | 12 ------------ src/main/java/org/duckdb/DuckDBTimestamp.java | 17 +++++++++++++++++ src/test/java/org/duckdb/TestDuckDBJDBC.java | 2 +- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/jni/duckdb_java.cpp b/src/jni/duckdb_java.cpp index 6d976b0b..217e07fd 100644 --- a/src/jni/duckdb_java.cpp +++ b/src/jni/duckdb_java.cpp @@ -38,6 +38,7 @@ static jclass J_Float; static jclass J_Double; static jclass J_String; static jclass J_Timestamp; +static jmethodID J_Timestamp_valueOf; static jclass J_TimestampTZ; static jclass J_Decimal; static jclass J_ByteArray; @@ -175,9 +176,12 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { tmpLocalRef = env->FindClass("java/lang/String"); J_String = (jclass)env->NewGlobalRef(tmpLocalRef); env->DeleteLocalRef(tmpLocalRef); + tmpLocalRef = env->FindClass("org/duckdb/DuckDBTimestamp"); J_Timestamp = (jclass)env->NewGlobalRef(tmpLocalRef); env->DeleteLocalRef(tmpLocalRef); + J_Timestamp_valueOf = env->GetStaticMethodID(J_Timestamp, "valueOf", "(Ljava/lang/Object;)Ljava/lang/Object;"); + tmpLocalRef = env->FindClass("org/duckdb/DuckDBTimestampTZ"); J_TimestampTZ = (jclass)env->NewGlobalRef(tmpLocalRef); env->DeleteLocalRef(tmpLocalRef); @@ -585,6 +589,8 @@ struct ResultHolder { }; Value ToValue(JNIEnv *env, jobject param, shared_ptr context) { + param = env->CallStaticObjectMethod(J_Timestamp, J_Timestamp_valueOf, param); + if (param == nullptr) { return (Value()); } else if (env->IsInstanceOf(param, J_Bool)) { diff --git a/src/main/java/org/duckdb/DuckDBPreparedStatement.java b/src/main/java/org/duckdb/DuckDBPreparedStatement.java index 6488ae0e..c7994651 100644 --- a/src/main/java/org/duckdb/DuckDBPreparedStatement.java +++ b/src/main/java/org/duckdb/DuckDBPreparedStatement.java @@ -239,18 +239,6 @@ public void setObject(int parameterIndex, Object x) throws SQLException { if (params.length == 0) { params = new Object[getParameterMetaData().getParameterCount()]; } - // Change sql.Timestamp to DuckDBTimestamp - if (x instanceof Timestamp) { - x = new DuckDBTimestamp((Timestamp) x); - } else if (x instanceof LocalDateTime) { - x = new DuckDBTimestamp((LocalDateTime) x); - } else if (x instanceof OffsetDateTime) { - x = new DuckDBTimestampTZ((OffsetDateTime) x); - } else if (x instanceof Date) { - x = new DuckDBDate((Date) x); - } else if (x instanceof Time) { - x = new DuckDBTime((Time) x); - } params[parameterIndex - 1] = x; } diff --git a/src/main/java/org/duckdb/DuckDBTimestamp.java b/src/main/java/org/duckdb/DuckDBTimestamp.java index b0b8f492..bc8819d2 100644 --- a/src/main/java/org/duckdb/DuckDBTimestamp.java +++ b/src/main/java/org/duckdb/DuckDBTimestamp.java @@ -1,6 +1,7 @@ package org.duckdb; import java.sql.Timestamp; +import java.sql.Time; import java.time.ZoneOffset; import java.time.Instant; import java.time.LocalDateTime; @@ -99,6 +100,22 @@ public static long localDateTime2Micros(LocalDateTime localDateTime) { return DuckDBTimestamp.RefLocalDateTime.until(localDateTime, ChronoUnit.MICROS); } + public static Object valueOf(Object x) { + // Change sql.Timestamp to DuckDBTimestamp + if (x instanceof Timestamp) { + x = new DuckDBTimestamp((Timestamp) x); + } else if (x instanceof LocalDateTime) { + x = new DuckDBTimestamp((LocalDateTime) x); + } else if (x instanceof OffsetDateTime) { + x = new DuckDBTimestampTZ((OffsetDateTime) x); + } else if (x instanceof Date) { + x = new DuckDBDate((Date) x); + } else if (x instanceof Time) { + x = new DuckDBTime((Time) x); + } + return x; + } + public Timestamp toSqlTimestamp() { return Timestamp.valueOf(this.toLocalDateTime()); } diff --git a/src/test/java/org/duckdb/TestDuckDBJDBC.java b/src/test/java/org/duckdb/TestDuckDBJDBC.java index d3d9f1f3..a6d0999f 100644 --- a/src/test/java/org/duckdb/TestDuckDBJDBC.java +++ b/src/test/java/org/duckdb/TestDuckDBJDBC.java @@ -3455,7 +3455,7 @@ private static void testStruct(Connection connection, Struct struct) throws SQLE Struct result = (Struct) rs.getObject(1); - assertEquals(Arrays.asList("1", "2"), Arrays.asList(result.getAttributes())); + assertEquals(Arrays.asList(1, 2), Arrays.asList(result.getAttributes())); } } } From 6deff7e800fad91f9379dc51b8d7fd09a5b3f38f Mon Sep 17 00:00:00 2001 From: Elliana May Date: Wed, 1 May 2024 00:11:18 +0800 Subject: [PATCH 08/15] add more struct tests --- src/test/java/org/duckdb/TestDuckDBJDBC.java | 32 ++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/test/java/org/duckdb/TestDuckDBJDBC.java b/src/test/java/org/duckdb/TestDuckDBJDBC.java index a6d0999f..493ab68c 100644 --- a/src/test/java/org/duckdb/TestDuckDBJDBC.java +++ b/src/test/java/org/duckdb/TestDuckDBJDBC.java @@ -3446,6 +3446,38 @@ public static void test_structs() throws Exception { } } + public static void test_struct_with_timestamp() throws Exception { + try (Connection connection = DriverManager.getConnection(JDBC_URL)) { + LocalDateTime now = LocalDateTime.of(LocalDate.of(2020, 5, 12), LocalTime.of(16, 20, 0, 0)); + Struct struct1 = connection.createStruct("STRUCT(start TIMESTAMP)", new Object[] {now}); + + try (PreparedStatement stmt = connection.prepareStatement("SELECT ?")) { + stmt.setObject(1, struct1); + + try (ResultSet rs = stmt.executeQuery()) { + assertTrue(rs.next()); + + Struct result = (Struct) rs.getObject(1); + + assertEquals(Timestamp.valueOf(now), result.getAttributes()[0]); + } + } + } + } + + public static void test_struct_with_bad_type() throws Exception { + try (Connection connection = DriverManager.getConnection(JDBC_URL)) { + Struct struct1 = connection.createStruct("BAD TYPE NAME", new Object[0]); + + try (PreparedStatement stmt = connection.prepareStatement("SELECT ?")) { + stmt.setObject(1, struct1); + String message = assertThrows(stmt::executeQuery, SQLException.class); + + assertTrue(message.contains("Parser Error: syntax error at or near \"TYPE\"")); + } + } + } + private static void testStruct(Connection connection, Struct struct) throws SQLException, Exception { try (PreparedStatement stmt = connection.prepareStatement("SELECT ?")) { stmt.setObject(1, struct); From ad77b3ce9e1973c5b9ec0d6e8eb1ef512ae40068 Mon Sep 17 00:00:00 2001 From: Elliana May Date: Wed, 1 May 2024 00:11:44 +0800 Subject: [PATCH 09/15] correct method call --- src/jni/duckdb_java.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jni/duckdb_java.cpp b/src/jni/duckdb_java.cpp index 217e07fd..597c4cce 100644 --- a/src/jni/duckdb_java.cpp +++ b/src/jni/duckdb_java.cpp @@ -640,7 +640,7 @@ Value ToValue(JNIEnv *env, jobject param, shared_ptr context) { auto entrySet = env->CallObjectMethod(param, J_Map_entrySet); auto iterator = env->CallObjectMethod(entrySet, J_Set_iterator); duckdb::vector entries; - while (env->CallObjectMethod(iterator, J_Iterator_hasNext)) { + while (env->CallBooleanMethod(iterator, J_Iterator_hasNext)) { auto entry = env->CallObjectMethod(iterator, J_Iterator_next); auto key = env->CallObjectMethod(entry, J_Entry_getKey); From 67edccc6eb1565c7126fab5d905f6842feb2d3f3 Mon Sep 17 00:00:00 2001 From: Elliana May Date: Wed, 1 May 2024 15:21:01 +0800 Subject: [PATCH 10/15] missing import --- src/main/java/org/duckdb/DuckDBTimestamp.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/duckdb/DuckDBTimestamp.java b/src/main/java/org/duckdb/DuckDBTimestamp.java index bc8819d2..c807991e 100644 --- a/src/main/java/org/duckdb/DuckDBTimestamp.java +++ b/src/main/java/org/duckdb/DuckDBTimestamp.java @@ -2,6 +2,7 @@ import java.sql.Timestamp; import java.sql.Time; +import java.util.Date; import java.time.ZoneOffset; import java.time.Instant; import java.time.LocalDateTime; From 8123c23e734388590f17cf40b03a312b26b1683f Mon Sep 17 00:00:00 2001 From: Elliana May Date: Wed, 1 May 2024 15:26:52 +0800 Subject: [PATCH 11/15] Update DuckDBTimestamp.java --- src/main/java/org/duckdb/DuckDBTimestamp.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/duckdb/DuckDBTimestamp.java b/src/main/java/org/duckdb/DuckDBTimestamp.java index c807991e..4a6ae46e 100644 --- a/src/main/java/org/duckdb/DuckDBTimestamp.java +++ b/src/main/java/org/duckdb/DuckDBTimestamp.java @@ -2,7 +2,7 @@ import java.sql.Timestamp; import java.sql.Time; -import java.util.Date; +import java.sql.Date; import java.time.ZoneOffset; import java.time.Instant; import java.time.LocalDateTime; From e44b7e8df798fcc20c6c28ceac5078a5112991d6 Mon Sep 17 00:00:00 2001 From: Elliana May Date: Thu, 2 May 2024 12:56:50 +0800 Subject: [PATCH 12/15] Update duckdb_java.cpp --- src/jni/duckdb_java.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/jni/duckdb_java.cpp b/src/jni/duckdb_java.cpp index 597c4cce..2e1302e3 100644 --- a/src/jni/duckdb_java.cpp +++ b/src/jni/duckdb_java.cpp @@ -77,9 +77,6 @@ static jclass J_Array; static jmethodID J_Array_getBaseTypeName; static jmethodID J_Array_getArray; -static jclass J_Object; -static jmethodID J_Object_toString; - static jclass J_DuckStruct; static jmethodID J_DuckStruct_init; @@ -102,6 +99,7 @@ static jmethodID J_UUID_getLeastSignificantBits; static jclass J_DuckDBDate; static jmethodID J_DuckDBDate_getDaysSinceEpoch; +static jclass J_Object; static jmethodID J_Object_toString; static jclass J_DuckDBTime; From 9e64e33de0d9d44751fd7c3d3580aecccc22e6f0 Mon Sep 17 00:00:00 2001 From: Elliana May Date: Thu, 2 May 2024 13:02:58 +0800 Subject: [PATCH 13/15] Replace to-do's --- src/jni/duckdb_java.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/jni/duckdb_java.cpp b/src/jni/duckdb_java.cpp index 2e1302e3..8c0ecd75 100644 --- a/src/jni/duckdb_java.cpp +++ b/src/jni/duckdb_java.cpp @@ -646,7 +646,7 @@ Value ToValue(JNIEnv *env, jobject param, shared_ptr context) { D_ASSERT(key); D_ASSERT(value); - entries.push_back(Value::STRUCT({{"key", GetString(env, key)}, {"value", GetString(env, value)}})); + entries.push_back(Value::STRUCT({{"key", ToValue(env, key, context)}, {"value", ToValue(env, value, context)}})); } return (Value::MAP(ListType::GetChildType(type), entries)); @@ -668,8 +668,7 @@ Value ToValue(JNIEnv *env, jobject param, shared_ptr context) { auto value = env->GetObjectArrayElement(jvalues, i); - // FIXME: use real type - values.emplace_back(name, GetString(env, value)); + values.emplace_back(name, ToValue(env, value, context)); } return (Value::STRUCT(std::move(values))); @@ -685,8 +684,7 @@ Value ToValue(JNIEnv *env, jobject param, shared_ptr context) { for (int i = 0; i < size; i++) { auto value = env->GetObjectArrayElement(jvalues, i); - // FIXME: use real type - values.emplace_back(GetString(env, value)); + values.emplace_back(ToValue(env, value, context)); } return (Value::LIST(type, values)); From cc95d4ef2428e65f240a60fd03ec88fe54eaf87f Mon Sep 17 00:00:00 2001 From: Elliana May Date: Mon, 10 Jun 2024 20:39:44 +0800 Subject: [PATCH 14/15] add todo --- src/main/java/org/duckdb/DuckDBTimestamp.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/duckdb/DuckDBTimestamp.java b/src/main/java/org/duckdb/DuckDBTimestamp.java index 4a6ae46e..8bd4f217 100644 --- a/src/main/java/org/duckdb/DuckDBTimestamp.java +++ b/src/main/java/org/duckdb/DuckDBTimestamp.java @@ -101,6 +101,7 @@ public static long localDateTime2Micros(LocalDateTime localDateTime) { return DuckDBTimestamp.RefLocalDateTime.until(localDateTime, ChronoUnit.MICROS); } + // TODO: move this to C++ side public static Object valueOf(Object x) { // Change sql.Timestamp to DuckDBTimestamp if (x instanceof Timestamp) { From f94aa2caba237938c95bcce57d9d2496ecfc24d1 Mon Sep 17 00:00:00 2001 From: Elliana May Date: Tue, 11 Jun 2024 00:09:40 +0800 Subject: [PATCH 15/15] add missing namespace --- src/jni/duckdb_java.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jni/duckdb_java.cpp b/src/jni/duckdb_java.cpp index 49ed4eb6..58d39b06 100644 --- a/src/jni/duckdb_java.cpp +++ b/src/jni/duckdb_java.cpp @@ -588,7 +588,7 @@ struct ResultHolder { duckdb::unique_ptr chunk; }; -Value ToValue(JNIEnv *env, jobject param, shared_ptr context) { +Value ToValue(JNIEnv *env, jobject param, duckdb::shared_ptr context) { param = env->CallStaticObjectMethod(J_Timestamp, J_Timestamp_valueOf, param); if (param == nullptr) {