Skip to content

Commit 329b9aa

Browse files
authored
Merge pull request #30 from Mause/feature/jdbc-struct
feat: struct, list, and map parameter support
2 parents b2393e0 + 456c7bf commit 329b9aa

File tree

8 files changed

+381
-73
lines changed

8 files changed

+381
-73
lines changed

src/jni/duckdb_java.cpp

Lines changed: 145 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ static jclass J_Float;
4040
static jclass J_Double;
4141
static jclass J_String;
4242
static jclass J_Timestamp;
43+
static jmethodID J_Timestamp_valueOf;
4344
static jclass J_TimestampTZ;
4445
static jclass J_Decimal;
4546
static jclass J_ByteArray;
@@ -70,11 +71,22 @@ static jfieldID J_DuckVector_varlen;
7071
static jclass J_DuckArray;
7172
static jmethodID J_DuckArray_init;
7273

74+
static jclass J_Struct;
75+
static jmethodID J_Struct_getSQLTypeName;
76+
static jmethodID J_Struct_getAttributes;
77+
78+
static jclass J_Array;
79+
static jmethodID J_Array_getBaseTypeName;
80+
static jmethodID J_Array_getArray;
81+
7382
static jclass J_DuckStruct;
7483
static jmethodID J_DuckStruct_init;
7584

7685
static jclass J_ByteBuffer;
7786

87+
static jclass J_DuckMap;
88+
static jmethodID J_DuckMap_getSQLTypeName;
89+
7890
static jmethodID J_Map_entrySet;
7991
static jmethodID J_Set_iterator;
8092
static jmethodID J_Iterator_hasNext;
@@ -89,6 +101,7 @@ static jmethodID J_UUID_getLeastSignificantBits;
89101
static jclass J_DuckDBDate;
90102
static jmethodID J_DuckDBDate_getDaysSinceEpoch;
91103

104+
static jclass J_Object;
92105
static jmethodID J_Object_toString;
93106

94107
static jclass J_DuckDBTime;
@@ -163,9 +176,12 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
163176
tmpLocalRef = env->FindClass("java/lang/String");
164177
J_String = (jclass)env->NewGlobalRef(tmpLocalRef);
165178
env->DeleteLocalRef(tmpLocalRef);
179+
166180
tmpLocalRef = env->FindClass("org/duckdb/DuckDBTimestamp");
167181
J_Timestamp = (jclass)env->NewGlobalRef(tmpLocalRef);
168182
env->DeleteLocalRef(tmpLocalRef);
183+
J_Timestamp_valueOf = env->GetStaticMethodID(J_Timestamp, "valueOf", "(Ljava/lang/Object;)Ljava/lang/Object;");
184+
169185
tmpLocalRef = env->FindClass("org/duckdb/DuckDBTimestampTZ");
170186
J_TimestampTZ = (jclass)env->NewGlobalRef(tmpLocalRef);
171187
env->DeleteLocalRef(tmpLocalRef);
@@ -183,6 +199,11 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
183199
J_ByteArray = (jclass)env->NewGlobalRef(tmpLocalRef);
184200
env->DeleteLocalRef(tmpLocalRef);
185201

202+
J_DuckMap = GetClassRef(env, "org/duckdb/user/DuckDBMap");
203+
D_ASSERT(J_DuckMap);
204+
J_DuckMap_getSQLTypeName = env->GetMethodID(J_DuckMap, "getSQLTypeName", "()Ljava/lang/String;");
205+
D_ASSERT(J_DuckMap_getSQLTypeName);
206+
186207
tmpLocalRef = env->FindClass("java/util/Map");
187208
J_Map_entrySet = env->GetMethodID(tmpLocalRef, "entrySet", "()Ljava/util/Set;");
188209
env->DeleteLocalRef(tmpLocalRef);
@@ -209,13 +230,21 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
209230
D_ASSERT(J_DuckArray_init);
210231
env->DeleteLocalRef(tmpLocalRef);
211232

212-
tmpLocalRef = env->FindClass("org/duckdb/DuckDBStruct");
213-
D_ASSERT(tmpLocalRef);
214-
J_DuckStruct = (jclass)env->NewGlobalRef(tmpLocalRef);
233+
J_DuckStruct = GetClassRef(env, "org/duckdb/DuckDBStruct");
215234
J_DuckStruct_init =
216235
env->GetMethodID(J_DuckStruct, "<init>", "([Ljava/lang/String;[Lorg/duckdb/DuckDBVector;ILjava/lang/String;)V");
217236
D_ASSERT(J_DuckStruct_init);
218-
env->DeleteLocalRef(tmpLocalRef);
237+
238+
J_Struct = GetClassRef(env, "java/sql/Struct");
239+
J_Struct_getSQLTypeName = env->GetMethodID(J_Struct, "getSQLTypeName", "()Ljava/lang/String;");
240+
J_Struct_getAttributes = env->GetMethodID(J_Struct, "getAttributes", "()[Ljava/lang/Object;");
241+
242+
J_Array = GetClassRef(env, "java/sql/Array");
243+
J_Array_getArray = env->GetMethodID(J_Array, "getArray", "()Ljava/lang/Object;");
244+
J_Array_getBaseTypeName = env->GetMethodID(J_Array, "getBaseTypeName", "()Ljava/lang/String;");
245+
246+
J_Object = GetClassRef(env, "java/lang/Object");
247+
J_Object_toString = env->GetMethodID(J_Object, "toString", "()Ljava/lang/String;");
219248

220249
tmpLocalRef = env->FindClass("java/util/Map$Entry");
221250
J_Entry_getKey = env->GetMethodID(tmpLocalRef, "getKey", "()Ljava/lang/Object;");
@@ -559,11 +588,120 @@ struct ResultHolder {
559588
duckdb::unique_ptr<DataChunk> chunk;
560589
};
561590

591+
Value ToValue(JNIEnv *env, jobject param, duckdb::shared_ptr<ClientContext> context) {
592+
param = env->CallStaticObjectMethod(J_Timestamp, J_Timestamp_valueOf, param);
593+
594+
if (param == nullptr) {
595+
return (Value());
596+
} else if (env->IsInstanceOf(param, J_Bool)) {
597+
return (Value::BOOLEAN(env->CallBooleanMethod(param, J_Bool_booleanValue)));
598+
} else if (env->IsInstanceOf(param, J_Byte)) {
599+
return (Value::TINYINT(env->CallByteMethod(param, J_Byte_byteValue)));
600+
} else if (env->IsInstanceOf(param, J_Short)) {
601+
return (Value::SMALLINT(env->CallShortMethod(param, J_Short_shortValue)));
602+
} else if (env->IsInstanceOf(param, J_Int)) {
603+
return (Value::INTEGER(env->CallIntMethod(param, J_Int_intValue)));
604+
} else if (env->IsInstanceOf(param, J_Long)) {
605+
return (Value::BIGINT(env->CallLongMethod(param, J_Long_longValue)));
606+
} else if (env->IsInstanceOf(param, J_TimestampTZ)) { // Check for subclass before superclass!
607+
return (
608+
Value::TIMESTAMPTZ((timestamp_t)env->CallLongMethod(param, J_TimestampTZ_getMicrosEpoch)));
609+
} else if (env->IsInstanceOf(param, J_DuckDBDate)) {
610+
return (
611+
Value::DATE((date_t)env->CallLongMethod(param, J_DuckDBDate_getDaysSinceEpoch)));
612+
613+
} else if (env->IsInstanceOf(param, J_DuckDBTime)) {
614+
return (Value::TIME((dtime_t)env->CallLongMethod(param, J_Timestamp_getMicrosEpoch)));
615+
} else if (env->IsInstanceOf(param, J_Timestamp)) {
616+
return (
617+
Value::TIMESTAMP((timestamp_t)env->CallLongMethod(param, J_Timestamp_getMicrosEpoch)));
618+
} else if (env->IsInstanceOf(param, J_Float)) {
619+
return (Value::FLOAT(env->CallFloatMethod(param, J_Float_floatValue)));
620+
} else if (env->IsInstanceOf(param, J_Double)) {
621+
return (Value::DOUBLE(env->CallDoubleMethod(param, J_Double_doubleValue)));
622+
} else if (env->IsInstanceOf(param, J_Decimal)) {
623+
Value val = create_value_from_bigdecimal(env, param);
624+
return (val);
625+
} else if (env->IsInstanceOf(param, J_String)) {
626+
auto param_string = jstring_to_string(env, (jstring)param);
627+
return (Value(param_string));
628+
} else if (env->IsInstanceOf(param, J_ByteArray)) {
629+
return (Value::BLOB_RAW(byte_array_to_string(env, (jbyteArray)param)));
630+
} else if (env->IsInstanceOf(param, J_UUID)) {
631+
auto most_significant = (jlong)env->CallObjectMethod(param, J_UUID_getMostSignificantBits);
632+
auto least_significant = (jlong)env->CallObjectMethod(param, J_UUID_getLeastSignificantBits);
633+
return (Value::UUID(hugeint_t(most_significant, least_significant)));
634+
} else if (env->IsInstanceOf(param, J_DuckMap)) {
635+
auto typeName = jstring_to_string(env, (jstring)env->CallObjectMethod(param, J_DuckMap_getSQLTypeName));
636+
637+
LogicalType type;
638+
context->RunFunctionInTransaction([&]() { type = TransformStringToLogicalType(typeName, *context); });
639+
640+
auto entrySet = env->CallObjectMethod(param, J_Map_entrySet);
641+
auto iterator = env->CallObjectMethod(entrySet, J_Set_iterator);
642+
duckdb::vector<Value> entries;
643+
while (env->CallBooleanMethod(iterator, J_Iterator_hasNext)) {
644+
auto entry = env->CallObjectMethod(iterator, J_Iterator_next);
645+
646+
auto key = env->CallObjectMethod(entry, J_Entry_getKey);
647+
auto value = env->CallObjectMethod(entry, J_Entry_getValue);
648+
D_ASSERT(key);
649+
D_ASSERT(value);
650+
651+
entries.push_back(Value::STRUCT({{"key", ToValue(env, key, context)}, {"value", ToValue(env, value, context)}}));
652+
}
653+
654+
return (Value::MAP(ListType::GetChildType(type), entries));
655+
656+
} else if (env->IsInstanceOf(param, J_Struct)) {
657+
auto typeName = jstring_to_string(env, (jstring)env->CallObjectMethod(param, J_Struct_getSQLTypeName));
658+
659+
LogicalType type;
660+
context->RunFunctionInTransaction([&]() { type = TransformStringToLogicalType(typeName, *context); });
661+
662+
auto jvalues = (jobjectArray)env->CallObjectMethod(param, J_Struct_getAttributes);
663+
664+
int size = env->GetArrayLength(jvalues);
665+
666+
child_list_t<Value> values;
667+
668+
for (int i = 0; i < size; i++) {
669+
auto name = StructType::GetChildName(type, i);
670+
671+
auto value = env->GetObjectArrayElement(jvalues, i);
672+
673+
values.emplace_back(name, ToValue(env, value, context));
674+
}
675+
676+
return (Value::STRUCT(std::move(values)));
677+
} else if (env->IsInstanceOf(param, J_Array)) {
678+
auto typeName = jstring_to_string(env, (jstring)env->CallObjectMethod(param, J_Array_getBaseTypeName));
679+
auto jvalues = (jobjectArray)env->CallObjectMethod(param, J_Array_getArray);
680+
int size = env->GetArrayLength(jvalues);
681+
682+
LogicalType type;
683+
context->RunFunctionInTransaction([&]() { type = TransformStringToLogicalType(typeName, *context); });
684+
685+
duckdb::vector<Value> values;
686+
for (int i = 0; i < size; i++) {
687+
auto value = env->GetObjectArrayElement(jvalues, i);
688+
689+
values.emplace_back(ToValue(env, value, context));
690+
}
691+
692+
return (Value::LIST(type, values));
693+
694+
} else {
695+
throw InvalidInputException("Unsupported parameter type");
696+
}
697+
}
698+
562699
jobject _duckdb_jdbc_execute(JNIEnv *env, jclass, jobject stmt_ref_buf, jobjectArray params) {
563700
auto stmt_ref = (StatementHolder *)env->GetDirectBufferAddress(stmt_ref_buf);
564701
if (!stmt_ref) {
565702
throw InvalidInputException("Invalid statement");
566703
}
704+
567705
auto res_ref = make_uniq<ResultHolder>();
568706
duckdb::vector<Value> duckdb_params;
569707

@@ -572,64 +710,12 @@ jobject _duckdb_jdbc_execute(JNIEnv *env, jclass, jobject stmt_ref_buf, jobjectA
572710
throw InvalidInputException("Parameter count mismatch");
573711
}
574712

713+
auto &context = stmt_ref->stmt->context;
714+
575715
if (param_len > 0) {
576716
for (idx_t i = 0; i < param_len; i++) {
577717
auto param = env->GetObjectArrayElement(params, i);
578-
if (param == nullptr) {
579-
duckdb_params.push_back(Value());
580-
continue;
581-
} else if (env->IsInstanceOf(param, J_Bool)) {
582-
duckdb_params.push_back(Value::BOOLEAN(env->CallBooleanMethod(param, J_Bool_booleanValue)));
583-
continue;
584-
} else if (env->IsInstanceOf(param, J_Byte)) {
585-
duckdb_params.push_back(Value::TINYINT(env->CallByteMethod(param, J_Byte_byteValue)));
586-
continue;
587-
} else if (env->IsInstanceOf(param, J_Short)) {
588-
duckdb_params.push_back(Value::SMALLINT(env->CallShortMethod(param, J_Short_shortValue)));
589-
continue;
590-
} else if (env->IsInstanceOf(param, J_Int)) {
591-
duckdb_params.push_back(Value::INTEGER(env->CallIntMethod(param, J_Int_intValue)));
592-
continue;
593-
} else if (env->IsInstanceOf(param, J_Long)) {
594-
duckdb_params.push_back(Value::BIGINT(env->CallLongMethod(param, J_Long_longValue)));
595-
continue;
596-
} else if (env->IsInstanceOf(param, J_TimestampTZ)) { // Check for subclass before superclass!
597-
duckdb_params.push_back(
598-
Value::TIMESTAMPTZ((timestamp_t)env->CallLongMethod(param, J_TimestampTZ_getMicrosEpoch)));
599-
continue;
600-
} else if (env->IsInstanceOf(param, J_DuckDBDate)) {
601-
duckdb_params.push_back(
602-
Value::DATE((date_t)env->CallLongMethod(param, J_DuckDBDate_getDaysSinceEpoch)));
603-
604-
} else if (env->IsInstanceOf(param, J_DuckDBTime)) {
605-
duckdb_params.push_back(Value::TIME((dtime_t)env->CallLongMethod(param, J_Timestamp_getMicrosEpoch)));
606-
607-
} else if (env->IsInstanceOf(param, J_Timestamp)) {
608-
duckdb_params.push_back(
609-
Value::TIMESTAMP((timestamp_t)env->CallLongMethod(param, J_Timestamp_getMicrosEpoch)));
610-
continue;
611-
} else if (env->IsInstanceOf(param, J_Float)) {
612-
duckdb_params.push_back(Value::FLOAT(env->CallFloatMethod(param, J_Float_floatValue)));
613-
continue;
614-
} else if (env->IsInstanceOf(param, J_Double)) {
615-
duckdb_params.push_back(Value::DOUBLE(env->CallDoubleMethod(param, J_Double_doubleValue)));
616-
continue;
617-
} else if (env->IsInstanceOf(param, J_Decimal)) {
618-
Value val = create_value_from_bigdecimal(env, param);
619-
duckdb_params.push_back(val);
620-
continue;
621-
} else if (env->IsInstanceOf(param, J_String)) {
622-
auto param_string = jstring_to_string(env, (jstring)param);
623-
duckdb_params.push_back(Value(param_string));
624-
} else if (env->IsInstanceOf(param, J_ByteArray)) {
625-
duckdb_params.push_back(Value::BLOB_RAW(byte_array_to_string(env, (jbyteArray)param)));
626-
} else if (env->IsInstanceOf(param, J_UUID)) {
627-
auto most_significant = (jlong)env->CallObjectMethod(param, J_UUID_getMostSignificantBits);
628-
auto least_significant = (jlong)env->CallObjectMethod(param, J_UUID_getLeastSignificantBits);
629-
duckdb_params.push_back(Value::UUID(hugeint_t(most_significant, least_significant)));
630-
} else {
631-
throw InvalidInputException("Unsupported parameter type");
632-
}
718+
duckdb_params.push_back(ToValue(env, param, context));
633719
}
634720
}
635721

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package org.duckdb;
22

3+
import org.duckdb.user.DuckDBMap;
4+
import org.duckdb.user.DuckDBUserArray;
5+
import org.duckdb.user.DuckDBUserStruct;
6+
37
import java.lang.reflect.InvocationTargetException;
48
import java.nio.ByteBuffer;
59
import java.nio.charset.StandardCharsets;
@@ -327,11 +331,15 @@ public Properties getClientInfo() throws SQLException {
327331
}
328332

329333
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
330-
throw new SQLFeatureNotSupportedException("createArrayOf");
334+
return new DuckDBUserArray(typeName, elements);
331335
}
332336

333337
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
334-
throw new SQLFeatureNotSupportedException("createStruct");
338+
return new DuckDBUserStruct(typeName, attributes);
339+
}
340+
341+
public <K, V> Map<K, V> createMap(String typeName, Map<K, V> map) {
342+
return new DuckDBMap<>(typeName, map);
335343
}
336344

337345
public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {

src/main/java/org/duckdb/DuckDBPreparedStatement.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -239,18 +239,6 @@ public void setObject(int parameterIndex, Object x) throws SQLException {
239239
if (params.length == 0) {
240240
params = new Object[getParameterMetaData().getParameterCount()];
241241
}
242-
// Change sql.Timestamp to DuckDBTimestamp
243-
if (x instanceof Timestamp) {
244-
x = new DuckDBTimestamp((Timestamp) x);
245-
} else if (x instanceof LocalDateTime) {
246-
x = new DuckDBTimestamp((LocalDateTime) x);
247-
} else if (x instanceof OffsetDateTime) {
248-
x = new DuckDBTimestampTZ((OffsetDateTime) x);
249-
} else if (x instanceof Date) {
250-
x = new DuckDBDate((Date) x);
251-
} else if (x instanceof Time) {
252-
x = new DuckDBTime((Time) x);
253-
}
254242
params[parameterIndex - 1] = x;
255243
}
256244

src/main/java/org/duckdb/DuckDBTimestamp.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.duckdb;
22

33
import java.sql.Timestamp;
4+
import java.sql.Time;
5+
import java.sql.Date;
46
import java.time.ZoneOffset;
57
import java.time.Instant;
68
import java.time.LocalDateTime;
@@ -99,6 +101,23 @@ public static long localDateTime2Micros(LocalDateTime localDateTime) {
99101
return DuckDBTimestamp.RefLocalDateTime.until(localDateTime, ChronoUnit.MICROS);
100102
}
101103

104+
// TODO: move this to C++ side
105+
public static Object valueOf(Object x) {
106+
// Change sql.Timestamp to DuckDBTimestamp
107+
if (x instanceof Timestamp) {
108+
x = new DuckDBTimestamp((Timestamp) x);
109+
} else if (x instanceof LocalDateTime) {
110+
x = new DuckDBTimestamp((LocalDateTime) x);
111+
} else if (x instanceof OffsetDateTime) {
112+
x = new DuckDBTimestampTZ((OffsetDateTime) x);
113+
} else if (x instanceof Date) {
114+
x = new DuckDBDate((Date) x);
115+
} else if (x instanceof Time) {
116+
x = new DuckDBTime((Time) x);
117+
}
118+
return x;
119+
}
120+
102121
public Timestamp toSqlTimestamp() {
103122
return Timestamp.valueOf(this.toLocalDateTime());
104123
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.duckdb.user;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
6+
public class DuckDBMap<K, V> extends HashMap<K, V> {
7+
private final String typeName;
8+
9+
public DuckDBMap(String typeName, Map<K, V> map) {
10+
super(map);
11+
this.typeName = typeName;
12+
}
13+
14+
public String getSQLTypeName() {
15+
return typeName;
16+
}
17+
}

0 commit comments

Comments
 (0)