Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 2 additions & 110 deletions src/jni/duckdb_java.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,115 +196,6 @@ jobject _duckdb_jdbc_prepare(JNIEnv *env, jclass, jobject conn_ref_buf, jbyteArr
return env->NewDirectByteBuffer(stmt_ref, 0);
}

Value ToValue(JNIEnv *env, jobject param, duckdb::shared_ptr<ClientContext> context) {
param = env->CallStaticObjectMethod(J_Timestamp, J_Timestamp_valueOf, param);

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_tz_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);
// Account for the following logic in UUID::FromString:
// Flip the first bit to make `order by uuid` same as `order by uuid::varchar`
most_significant ^= (std::numeric_limits<int64_t>::min)();
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<Value> entries;
while (env->CallBooleanMethod(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", ToValue(env, key, context)}, {"value", ToValue(env, value, context)}}));
}

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<Value> values;

for (int i = 0; i < size; i++) {
auto name = StructType::GetChildName(type, i);

auto value = env->GetObjectArrayElement(jvalues, i);

values.emplace_back(name, ToValue(env, value, context));
}

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<Value> values;
for (int i = 0; i < size; i++) {
auto value = env->GetObjectArrayElement(jvalues, i);

values.emplace_back(ToValue(env, value, context));
}

return (Value::LIST(type, values));

} else {
throw InvalidInputException("Unsupported parameter type");
}
}

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) {
Expand All @@ -325,7 +216,8 @@ 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);
duckdb_params.push_back(ToValue(env, param, context));
duckdb::Value val = to_duckdb_value(env, param, *context);
duckdb_params.push_back(std::move(val));
}
}

Expand Down
30 changes: 18 additions & 12 deletions src/jni/refs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ jclass J_String;
jclass J_Timestamp;
jmethodID J_Timestamp_valueOf;
jclass J_TimestampTZ;
jclass J_Decimal;
jclass J_BigDecimal;
jclass J_HugeInt;
jclass J_ByteArray;

jmethodID J_Bool_booleanValue;
Expand All @@ -42,11 +43,13 @@ jmethodID J_Float_floatValue;
jmethodID J_Double_doubleValue;
jmethodID J_Timestamp_getMicrosEpoch;
jmethodID J_TimestampTZ_getMicrosEpoch;
jmethodID J_Decimal_precision;
jmethodID J_Decimal_scale;
jmethodID J_Decimal_scaleByPowTen;
jmethodID J_Decimal_toPlainString;
jmethodID J_Decimal_longValue;
jmethodID J_BigDecimal_precision;
jmethodID J_BigDecimal_scale;
jmethodID J_BigDecimal_scaleByPowTen;
jmethodID J_BigDecimal_toPlainString;
jmethodID J_BigDecimal_longValue;
jfieldID J_HugeInt_lower;
jfieldID J_HugeInt_upper;

jclass J_DuckResultSetMeta;
jmethodID J_DuckResultSetMeta_init;
Expand Down Expand Up @@ -181,7 +184,8 @@ void create_refs(JNIEnv *env) {
J_Float = make_class_ref(env, "java/lang/Float");
J_Double = make_class_ref(env, "java/lang/Double");
J_String = make_class_ref(env, "java/lang/String");
J_Decimal = make_class_ref(env, "java/math/BigDecimal");
J_BigDecimal = make_class_ref(env, "java/math/BigDecimal");
J_HugeInt = make_class_ref(env, "org/duckdb/DuckDBHugeInt");
J_ByteArray = make_class_ref(env, "[B");

J_Timestamp = make_class_ref(env, "org/duckdb/DuckDBTimestamp");
Expand Down Expand Up @@ -240,11 +244,13 @@ void create_refs(JNIEnv *env) {
J_Double_doubleValue = get_method_id(env, J_Double, "doubleValue", "()D");
J_Timestamp_getMicrosEpoch = get_method_id(env, J_Timestamp, "getMicrosEpoch", "()J");
J_TimestampTZ_getMicrosEpoch = get_method_id(env, J_TimestampTZ, "getMicrosEpoch", "()J");
J_Decimal_precision = get_method_id(env, J_Decimal, "precision", "()I");
J_Decimal_scale = get_method_id(env, J_Decimal, "scale", "()I");
J_Decimal_scaleByPowTen = get_method_id(env, J_Decimal, "scaleByPowerOfTen", "(I)Ljava/math/BigDecimal;");
J_Decimal_toPlainString = get_method_id(env, J_Decimal, "toPlainString", "()Ljava/lang/String;");
J_Decimal_longValue = get_method_id(env, J_Decimal, "longValue", "()J");
J_BigDecimal_precision = get_method_id(env, J_BigDecimal, "precision", "()I");
J_BigDecimal_scale = get_method_id(env, J_BigDecimal, "scale", "()I");
J_BigDecimal_scaleByPowTen = get_method_id(env, J_BigDecimal, "scaleByPowerOfTen", "(I)Ljava/math/BigDecimal;");
J_BigDecimal_toPlainString = get_method_id(env, J_BigDecimal, "toPlainString", "()Ljava/lang/String;");
J_BigDecimal_longValue = get_method_id(env, J_BigDecimal, "longValue", "()J");
J_HugeInt_lower = get_field_id(env, J_HugeInt, "lower", "J");
J_HugeInt_upper = get_field_id(env, J_HugeInt, "upper", "J");

J_DuckResultSetMeta = make_class_ref(env, "org/duckdb/DuckDBResultSetMetaData");
J_DuckResultSetMeta_init = env->GetMethodID(J_DuckResultSetMeta, "<init>",
Expand Down
15 changes: 9 additions & 6 deletions src/jni/refs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ extern jclass J_String;
extern jclass J_Timestamp;
extern jmethodID J_Timestamp_valueOf;
extern jclass J_TimestampTZ;
extern jclass J_Decimal;
extern jclass J_BigDecimal;
extern jclass J_HugeInt;
extern jclass J_ByteArray;

extern jmethodID J_Bool_booleanValue;
Expand All @@ -39,11 +40,13 @@ extern jmethodID J_Float_floatValue;
extern jmethodID J_Double_doubleValue;
extern jmethodID J_Timestamp_getMicrosEpoch;
extern jmethodID J_TimestampTZ_getMicrosEpoch;
extern jmethodID J_Decimal_precision;
extern jmethodID J_Decimal_scale;
extern jmethodID J_Decimal_scaleByPowTen;
extern jmethodID J_Decimal_toPlainString;
extern jmethodID J_Decimal_longValue;
extern jmethodID J_BigDecimal_precision;
extern jmethodID J_BigDecimal_scale;
extern jmethodID J_BigDecimal_scaleByPowTen;
extern jmethodID J_BigDecimal_toPlainString;
extern jmethodID J_BigDecimal_longValue;
extern jfieldID J_HugeInt_lower;
extern jfieldID J_HugeInt_upper;

extern jclass J_DuckResultSetMeta;
extern jmethodID J_DuckResultSetMeta_init;
Expand Down
167 changes: 167 additions & 0 deletions src/jni/types.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#include "types.hpp"

#include "refs.hpp"
#include "util.hpp"

#include <string>
#include <vector>

Expand Down Expand Up @@ -43,3 +46,167 @@ std::string type_to_jduckdb_type(duckdb::LogicalType logical_type) {
return duckdb::EnumUtil::ToString(logical_type.id());
}
}

duckdb::Value create_value_from_bigdecimal(JNIEnv *env, jobject decimal) {
jint precision = env->CallIntMethod(decimal, J_BigDecimal_precision);
jint scale = env->CallIntMethod(decimal, J_BigDecimal_scale);

// Java BigDecimal type can have scale that exceeds the precision
// Which our DECIMAL type does not support (assert(width >= scale))
if (scale > precision) {
precision = scale;
}

// DECIMAL scale is unsigned, so negative values are not supported
if (scale < 0) {
throw duckdb::InvalidInputException("Converting from a BigDecimal with negative scale is not supported");
}

duckdb::Value val;

if (precision <= 18) { // normal sizes -> avoid string processing
jobject no_point_dec = env->CallObjectMethod(decimal, J_BigDecimal_scaleByPowTen, scale);
jlong result = env->CallLongMethod(no_point_dec, J_BigDecimal_longValue);
val = duckdb::Value::DECIMAL((int64_t)result, (uint8_t)precision, (uint8_t)scale);
} else if (precision <= 38) { // larger than int64 -> get string and cast
jobject str_val = env->CallObjectMethod(decimal, J_BigDecimal_toPlainString);
auto *str_char = env->GetStringUTFChars((jstring)str_val, 0);
val = duckdb::Value(str_char);
val = val.DefaultCastAs(duckdb::LogicalType::DECIMAL(precision, scale));
env->ReleaseStringUTFChars((jstring)str_val, str_char);
}

return val;
}

static duckdb::Value create_value_from_hugeint(JNIEnv *env, jobject hugeint) {
jlong lower = env->GetLongField(hugeint, J_HugeInt_lower);
jlong upper = env->GetLongField(hugeint, J_HugeInt_upper);
duckdb::hugeint_t hi(upper, lower);
return duckdb::Value::HUGEINT(std::move(hi));
}

static duckdb::Value create_value_from_uuid(JNIEnv *env, jobject param) {
jlong most_significant = env->CallLongMethod(param, J_UUID_getMostSignificantBits);
// Account for the following logic in UUID::FromString:
// Flip the first bit to make `order by uuid` same as `order by uuid::varchar`
most_significant ^= (std::numeric_limits<int64_t>::min)();
jlong least_significant = env->CallLongMethod(param, J_UUID_getLeastSignificantBits);
duckdb::hugeint_t hi = duckdb::hugeint_t(most_significant, least_significant);
return duckdb::Value::UUID(std::move(hi));
}

static duckdb::Value create_value_from_map(JNIEnv *env, jobject param, duckdb::ClientContext &context) {
auto typeName = jstring_to_string(env, (jstring)env->CallObjectMethod(param, J_DuckMap_getSQLTypeName));

duckdb::LogicalType type;
context.RunFunctionInTransaction([&]() { type = duckdb::TransformStringToLogicalType(typeName, context); });

auto entrySet = env->CallObjectMethod(param, J_Map_entrySet);
auto iterator = env->CallObjectMethod(entrySet, J_Set_iterator);
duckdb::vector<duckdb::Value> entries;
while (env->CallBooleanMethod(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(duckdb::Value::STRUCT(
{{"key", to_duckdb_value(env, key, context)}, {"value", to_duckdb_value(env, value, context)}}));
}

return duckdb::Value::MAP(duckdb::ListType::GetChildType(type), entries);
}

static duckdb::Value create_value_from_struct(JNIEnv *env, jobject param, duckdb::ClientContext &context) {
auto typeName = jstring_to_string(env, (jstring)env->CallObjectMethod(param, J_Struct_getSQLTypeName));

duckdb::LogicalType type;
context.RunFunctionInTransaction([&]() { type = TransformStringToLogicalType(typeName, context); });

auto jvalues = (jobjectArray)env->CallObjectMethod(param, J_Struct_getAttributes);

int size = env->GetArrayLength(jvalues);

duckdb::child_list_t<duckdb::Value> values;

for (int i = 0; i < size; i++) {
auto name = duckdb::StructType::GetChildName(type, i);

auto value = env->GetObjectArrayElement(jvalues, i);

values.emplace_back(name, to_duckdb_value(env, value, context));
}

return duckdb::Value::STRUCT(std::move(values));
}

static duckdb::Value create_value_from_array(JNIEnv *env, jobject param, duckdb::ClientContext &context) {
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);

duckdb::LogicalType type;
context.RunFunctionInTransaction([&]() { type = TransformStringToLogicalType(typeName, context); });

duckdb::vector<duckdb::Value> values;
for (int i = 0; i < size; i++) {
auto value = env->GetObjectArrayElement(jvalues, i);

values.emplace_back(to_duckdb_value(env, value, context));
}

return (duckdb::Value::LIST(type, values));
}

duckdb::Value to_duckdb_value(JNIEnv *env, jobject param, duckdb::ClientContext &context) {
param = env->CallStaticObjectMethod(J_Timestamp, J_Timestamp_valueOf, param);

if (param == nullptr) {
return (duckdb::Value());
} else if (env->IsInstanceOf(param, J_Bool)) {
return (duckdb::Value::BOOLEAN(env->CallBooleanMethod(param, J_Bool_booleanValue)));
} else if (env->IsInstanceOf(param, J_Byte)) {
return (duckdb::Value::TINYINT(env->CallByteMethod(param, J_Byte_byteValue)));
} else if (env->IsInstanceOf(param, J_Short)) {
return (duckdb::Value::SMALLINT(env->CallShortMethod(param, J_Short_shortValue)));
} else if (env->IsInstanceOf(param, J_Int)) {
return (duckdb::Value::INTEGER(env->CallIntMethod(param, J_Int_intValue)));
} else if (env->IsInstanceOf(param, J_Long)) {
return (duckdb::Value::BIGINT(env->CallLongMethod(param, J_Long_longValue)));
} else if (env->IsInstanceOf(param, J_HugeInt)) {
return create_value_from_hugeint(env, param);
} else if (env->IsInstanceOf(param, J_TimestampTZ)) { // Check for subclass before superclass!
return (duckdb::Value::TIMESTAMPTZ(
(duckdb::timestamp_tz_t)env->CallLongMethod(param, J_TimestampTZ_getMicrosEpoch)));
} else if (env->IsInstanceOf(param, J_DuckDBDate)) {
return (duckdb::Value::DATE((duckdb::date_t)env->CallLongMethod(param, J_DuckDBDate_getDaysSinceEpoch)));
} else if (env->IsInstanceOf(param, J_DuckDBTime)) {
return (duckdb::Value::TIME((duckdb::dtime_t)env->CallLongMethod(param, J_Timestamp_getMicrosEpoch)));
} else if (env->IsInstanceOf(param, J_Timestamp)) {
return (duckdb::Value::TIMESTAMP((duckdb::timestamp_t)env->CallLongMethod(param, J_Timestamp_getMicrosEpoch)));
} else if (env->IsInstanceOf(param, J_Float)) {
return (duckdb::Value::FLOAT(env->CallFloatMethod(param, J_Float_floatValue)));
} else if (env->IsInstanceOf(param, J_Double)) {
return (duckdb::Value::DOUBLE(env->CallDoubleMethod(param, J_Double_doubleValue)));
} else if (env->IsInstanceOf(param, J_BigDecimal)) {
return create_value_from_bigdecimal(env, param);
} else if (env->IsInstanceOf(param, J_String)) {
auto param_string = jstring_to_string(env, (jstring)param);
return (duckdb::Value(param_string));
} else if (env->IsInstanceOf(param, J_ByteArray)) {
return (duckdb::Value::BLOB_RAW(byte_array_to_string(env, (jbyteArray)param)));
} else if (env->IsInstanceOf(param, J_UUID)) {
return create_value_from_uuid(env, param);
} else if (env->IsInstanceOf(param, J_DuckMap)) {
return create_value_from_map(env, param, context);
} else if (env->IsInstanceOf(param, J_Struct)) {
return create_value_from_struct(env, param, context);
} else if (env->IsInstanceOf(param, J_Array)) {
return create_value_from_array(env, param, context);
} else {
throw duckdb::InvalidInputException("Unsupported parameter type");
}
}
Loading