Skip to content

Commit be215e7

Browse files
committed
Result set enhancements for integer types
This change improves support for integer types in `ResultSet`. It allows to fetch more narrow types (like `SMALLINT`) using `getObject()` calls with more wide type parameter (like `BIGINT`). It also adds support for fethching `HUGEINT` values as instances of a `BigInteger` class and adds new methods to `Connection` to be able to insert 128-bit `HUGEINT` values passing them as a pair of `long`'s or as a `BigInteger`. Additonally it corrects JDBC types mapping for integer types and changes the fallback JDBC type from `JAVA_OBJECT` to `OTHER`. Testing: new tests are added to cover integer values fetching from `ResultSet`; existing `ResultSet` tests moved to separate files. Fixes: duckdb#204
1 parent 747d5f5 commit be215e7

17 files changed

+1141
-761
lines changed

src/jni/duckdb_java.cpp

Lines changed: 2 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -196,115 +196,6 @@ jobject _duckdb_jdbc_prepare(JNIEnv *env, jclass, jobject conn_ref_buf, jbyteArr
196196
return env->NewDirectByteBuffer(stmt_ref, 0);
197197
}
198198

199-
Value ToValue(JNIEnv *env, jobject param, duckdb::shared_ptr<ClientContext> context) {
200-
param = env->CallStaticObjectMethod(J_Timestamp, J_Timestamp_valueOf, param);
201-
202-
if (param == nullptr) {
203-
return (Value());
204-
} else if (env->IsInstanceOf(param, J_Bool)) {
205-
return (Value::BOOLEAN(env->CallBooleanMethod(param, J_Bool_booleanValue)));
206-
} else if (env->IsInstanceOf(param, J_Byte)) {
207-
return (Value::TINYINT(env->CallByteMethod(param, J_Byte_byteValue)));
208-
} else if (env->IsInstanceOf(param, J_Short)) {
209-
return (Value::SMALLINT(env->CallShortMethod(param, J_Short_shortValue)));
210-
} else if (env->IsInstanceOf(param, J_Int)) {
211-
return (Value::INTEGER(env->CallIntMethod(param, J_Int_intValue)));
212-
} else if (env->IsInstanceOf(param, J_Long)) {
213-
return (Value::BIGINT(env->CallLongMethod(param, J_Long_longValue)));
214-
} else if (env->IsInstanceOf(param, J_TimestampTZ)) { // Check for subclass before superclass!
215-
return (Value::TIMESTAMPTZ((timestamp_tz_t)env->CallLongMethod(param, J_TimestampTZ_getMicrosEpoch)));
216-
} else if (env->IsInstanceOf(param, J_DuckDBDate)) {
217-
return (Value::DATE((date_t)env->CallLongMethod(param, J_DuckDBDate_getDaysSinceEpoch)));
218-
219-
} else if (env->IsInstanceOf(param, J_DuckDBTime)) {
220-
return (Value::TIME((dtime_t)env->CallLongMethod(param, J_Timestamp_getMicrosEpoch)));
221-
} else if (env->IsInstanceOf(param, J_Timestamp)) {
222-
return (Value::TIMESTAMP((timestamp_t)env->CallLongMethod(param, J_Timestamp_getMicrosEpoch)));
223-
} else if (env->IsInstanceOf(param, J_Float)) {
224-
return (Value::FLOAT(env->CallFloatMethod(param, J_Float_floatValue)));
225-
} else if (env->IsInstanceOf(param, J_Double)) {
226-
return (Value::DOUBLE(env->CallDoubleMethod(param, J_Double_doubleValue)));
227-
} else if (env->IsInstanceOf(param, J_Decimal)) {
228-
Value val = create_value_from_bigdecimal(env, param);
229-
return (val);
230-
} else if (env->IsInstanceOf(param, J_String)) {
231-
auto param_string = jstring_to_string(env, (jstring)param);
232-
return (Value(param_string));
233-
} else if (env->IsInstanceOf(param, J_ByteArray)) {
234-
return (Value::BLOB_RAW(byte_array_to_string(env, (jbyteArray)param)));
235-
} else if (env->IsInstanceOf(param, J_UUID)) {
236-
auto most_significant = (jlong)env->CallObjectMethod(param, J_UUID_getMostSignificantBits);
237-
// Account for the following logic in UUID::FromString:
238-
// Flip the first bit to make `order by uuid` same as `order by uuid::varchar`
239-
most_significant ^= (std::numeric_limits<int64_t>::min)();
240-
auto least_significant = (jlong)env->CallObjectMethod(param, J_UUID_getLeastSignificantBits);
241-
return (Value::UUID(hugeint_t(most_significant, least_significant)));
242-
} else if (env->IsInstanceOf(param, J_DuckMap)) {
243-
auto typeName = jstring_to_string(env, (jstring)env->CallObjectMethod(param, J_DuckMap_getSQLTypeName));
244-
245-
LogicalType type;
246-
context->RunFunctionInTransaction([&]() { type = TransformStringToLogicalType(typeName, *context); });
247-
248-
auto entrySet = env->CallObjectMethod(param, J_Map_entrySet);
249-
auto iterator = env->CallObjectMethod(entrySet, J_Set_iterator);
250-
duckdb::vector<Value> entries;
251-
while (env->CallBooleanMethod(iterator, J_Iterator_hasNext)) {
252-
auto entry = env->CallObjectMethod(iterator, J_Iterator_next);
253-
254-
auto key = env->CallObjectMethod(entry, J_Entry_getKey);
255-
auto value = env->CallObjectMethod(entry, J_Entry_getValue);
256-
D_ASSERT(key);
257-
D_ASSERT(value);
258-
259-
entries.push_back(
260-
Value::STRUCT({{"key", ToValue(env, key, context)}, {"value", ToValue(env, value, context)}}));
261-
}
262-
263-
return (Value::MAP(ListType::GetChildType(type), entries));
264-
265-
} else if (env->IsInstanceOf(param, J_Struct)) {
266-
auto typeName = jstring_to_string(env, (jstring)env->CallObjectMethod(param, J_Struct_getSQLTypeName));
267-
268-
LogicalType type;
269-
context->RunFunctionInTransaction([&]() { type = TransformStringToLogicalType(typeName, *context); });
270-
271-
auto jvalues = (jobjectArray)env->CallObjectMethod(param, J_Struct_getAttributes);
272-
273-
int size = env->GetArrayLength(jvalues);
274-
275-
child_list_t<Value> values;
276-
277-
for (int i = 0; i < size; i++) {
278-
auto name = StructType::GetChildName(type, i);
279-
280-
auto value = env->GetObjectArrayElement(jvalues, i);
281-
282-
values.emplace_back(name, ToValue(env, value, context));
283-
}
284-
285-
return (Value::STRUCT(std::move(values)));
286-
} else if (env->IsInstanceOf(param, J_Array)) {
287-
auto typeName = jstring_to_string(env, (jstring)env->CallObjectMethod(param, J_Array_getBaseTypeName));
288-
auto jvalues = (jobjectArray)env->CallObjectMethod(param, J_Array_getArray);
289-
int size = env->GetArrayLength(jvalues);
290-
291-
LogicalType type;
292-
context->RunFunctionInTransaction([&]() { type = TransformStringToLogicalType(typeName, *context); });
293-
294-
duckdb::vector<Value> values;
295-
for (int i = 0; i < size; i++) {
296-
auto value = env->GetObjectArrayElement(jvalues, i);
297-
298-
values.emplace_back(ToValue(env, value, context));
299-
}
300-
301-
return (Value::LIST(type, values));
302-
303-
} else {
304-
throw InvalidInputException("Unsupported parameter type");
305-
}
306-
}
307-
308199
jobject _duckdb_jdbc_execute(JNIEnv *env, jclass, jobject stmt_ref_buf, jobjectArray params) {
309200
auto stmt_ref = (StatementHolder *)env->GetDirectBufferAddress(stmt_ref_buf);
310201
if (!stmt_ref) {
@@ -325,7 +216,8 @@ jobject _duckdb_jdbc_execute(JNIEnv *env, jclass, jobject stmt_ref_buf, jobjectA
325216
if (param_len > 0) {
326217
for (idx_t i = 0; i < param_len; i++) {
327218
auto param = env->GetObjectArrayElement(params, i);
328-
duckdb_params.push_back(ToValue(env, param, context));
219+
duckdb::Value val = to_duckdb_value(env, param, *context);
220+
duckdb_params.push_back(std::move(val));
329221
}
330222
}
331223

src/jni/refs.cpp

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ jclass J_String;
3030
jclass J_Timestamp;
3131
jmethodID J_Timestamp_valueOf;
3232
jclass J_TimestampTZ;
33-
jclass J_Decimal;
33+
jclass J_BigDecimal;
34+
jclass J_HugeInt;
3435
jclass J_ByteArray;
3536

3637
jmethodID J_Bool_booleanValue;
@@ -42,11 +43,13 @@ jmethodID J_Float_floatValue;
4243
jmethodID J_Double_doubleValue;
4344
jmethodID J_Timestamp_getMicrosEpoch;
4445
jmethodID J_TimestampTZ_getMicrosEpoch;
45-
jmethodID J_Decimal_precision;
46-
jmethodID J_Decimal_scale;
47-
jmethodID J_Decimal_scaleByPowTen;
48-
jmethodID J_Decimal_toPlainString;
49-
jmethodID J_Decimal_longValue;
46+
jmethodID J_BigDecimal_precision;
47+
jmethodID J_BigDecimal_scale;
48+
jmethodID J_BigDecimal_scaleByPowTen;
49+
jmethodID J_BigDecimal_toPlainString;
50+
jmethodID J_BigDecimal_longValue;
51+
jfieldID J_HugeInt_lower;
52+
jfieldID J_HugeInt_upper;
5053

5154
jclass J_DuckResultSetMeta;
5255
jmethodID J_DuckResultSetMeta_init;
@@ -181,7 +184,8 @@ void create_refs(JNIEnv *env) {
181184
J_Float = make_class_ref(env, "java/lang/Float");
182185
J_Double = make_class_ref(env, "java/lang/Double");
183186
J_String = make_class_ref(env, "java/lang/String");
184-
J_Decimal = make_class_ref(env, "java/math/BigDecimal");
187+
J_BigDecimal = make_class_ref(env, "java/math/BigDecimal");
188+
J_HugeInt = make_class_ref(env, "org/duckdb/DuckDBHugeInt");
185189
J_ByteArray = make_class_ref(env, "[B");
186190

187191
J_Timestamp = make_class_ref(env, "org/duckdb/DuckDBTimestamp");
@@ -240,11 +244,13 @@ void create_refs(JNIEnv *env) {
240244
J_Double_doubleValue = get_method_id(env, J_Double, "doubleValue", "()D");
241245
J_Timestamp_getMicrosEpoch = get_method_id(env, J_Timestamp, "getMicrosEpoch", "()J");
242246
J_TimestampTZ_getMicrosEpoch = get_method_id(env, J_TimestampTZ, "getMicrosEpoch", "()J");
243-
J_Decimal_precision = get_method_id(env, J_Decimal, "precision", "()I");
244-
J_Decimal_scale = get_method_id(env, J_Decimal, "scale", "()I");
245-
J_Decimal_scaleByPowTen = get_method_id(env, J_Decimal, "scaleByPowerOfTen", "(I)Ljava/math/BigDecimal;");
246-
J_Decimal_toPlainString = get_method_id(env, J_Decimal, "toPlainString", "()Ljava/lang/String;");
247-
J_Decimal_longValue = get_method_id(env, J_Decimal, "longValue", "()J");
247+
J_BigDecimal_precision = get_method_id(env, J_BigDecimal, "precision", "()I");
248+
J_BigDecimal_scale = get_method_id(env, J_BigDecimal, "scale", "()I");
249+
J_BigDecimal_scaleByPowTen = get_method_id(env, J_BigDecimal, "scaleByPowerOfTen", "(I)Ljava/math/BigDecimal;");
250+
J_BigDecimal_toPlainString = get_method_id(env, J_BigDecimal, "toPlainString", "()Ljava/lang/String;");
251+
J_BigDecimal_longValue = get_method_id(env, J_BigDecimal, "longValue", "()J");
252+
J_HugeInt_lower = get_field_id(env, J_HugeInt, "lower", "J");
253+
J_HugeInt_upper = get_field_id(env, J_HugeInt, "upper", "J");
248254

249255
J_DuckResultSetMeta = make_class_ref(env, "org/duckdb/DuckDBResultSetMetaData");
250256
J_DuckResultSetMeta_init = env->GetMethodID(J_DuckResultSetMeta, "<init>",

src/jni/refs.hpp

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ extern jclass J_String;
2727
extern jclass J_Timestamp;
2828
extern jmethodID J_Timestamp_valueOf;
2929
extern jclass J_TimestampTZ;
30-
extern jclass J_Decimal;
30+
extern jclass J_BigDecimal;
31+
extern jclass J_HugeInt;
3132
extern jclass J_ByteArray;
3233

3334
extern jmethodID J_Bool_booleanValue;
@@ -39,11 +40,13 @@ extern jmethodID J_Float_floatValue;
3940
extern jmethodID J_Double_doubleValue;
4041
extern jmethodID J_Timestamp_getMicrosEpoch;
4142
extern jmethodID J_TimestampTZ_getMicrosEpoch;
42-
extern jmethodID J_Decimal_precision;
43-
extern jmethodID J_Decimal_scale;
44-
extern jmethodID J_Decimal_scaleByPowTen;
45-
extern jmethodID J_Decimal_toPlainString;
46-
extern jmethodID J_Decimal_longValue;
43+
extern jmethodID J_BigDecimal_precision;
44+
extern jmethodID J_BigDecimal_scale;
45+
extern jmethodID J_BigDecimal_scaleByPowTen;
46+
extern jmethodID J_BigDecimal_toPlainString;
47+
extern jmethodID J_BigDecimal_longValue;
48+
extern jfieldID J_HugeInt_lower;
49+
extern jfieldID J_HugeInt_upper;
4750

4851
extern jclass J_DuckResultSetMeta;
4952
extern jmethodID J_DuckResultSetMeta_init;

src/jni/types.cpp

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#include "types.hpp"
22

3+
#include "refs.hpp"
4+
#include "util.hpp"
5+
36
#include <string>
47
#include <vector>
58

@@ -43,3 +46,167 @@ std::string type_to_jduckdb_type(duckdb::LogicalType logical_type) {
4346
return duckdb::EnumUtil::ToString(logical_type.id());
4447
}
4548
}
49+
50+
duckdb::Value create_value_from_bigdecimal(JNIEnv *env, jobject decimal) {
51+
jint precision = env->CallIntMethod(decimal, J_BigDecimal_precision);
52+
jint scale = env->CallIntMethod(decimal, J_BigDecimal_scale);
53+
54+
// Java BigDecimal type can have scale that exceeds the precision
55+
// Which our DECIMAL type does not support (assert(width >= scale))
56+
if (scale > precision) {
57+
precision = scale;
58+
}
59+
60+
// DECIMAL scale is unsigned, so negative values are not supported
61+
if (scale < 0) {
62+
throw duckdb::InvalidInputException("Converting from a BigDecimal with negative scale is not supported");
63+
}
64+
65+
duckdb::Value val;
66+
67+
if (precision <= 18) { // normal sizes -> avoid string processing
68+
jobject no_point_dec = env->CallObjectMethod(decimal, J_BigDecimal_scaleByPowTen, scale);
69+
jlong result = env->CallLongMethod(no_point_dec, J_BigDecimal_longValue);
70+
val = duckdb::Value::DECIMAL((int64_t)result, (uint8_t)precision, (uint8_t)scale);
71+
} else if (precision <= 38) { // larger than int64 -> get string and cast
72+
jobject str_val = env->CallObjectMethod(decimal, J_BigDecimal_toPlainString);
73+
auto *str_char = env->GetStringUTFChars((jstring)str_val, 0);
74+
val = duckdb::Value(str_char);
75+
val = val.DefaultCastAs(duckdb::LogicalType::DECIMAL(precision, scale));
76+
env->ReleaseStringUTFChars((jstring)str_val, str_char);
77+
}
78+
79+
return val;
80+
}
81+
82+
static duckdb::Value create_value_from_hugeint(JNIEnv *env, jobject hugeint) {
83+
jlong lower = env->GetLongField(hugeint, J_HugeInt_lower);
84+
jlong upper = env->GetLongField(hugeint, J_HugeInt_upper);
85+
duckdb::hugeint_t hi(upper, lower);
86+
return duckdb::Value::HUGEINT(std::move(hi));
87+
}
88+
89+
static duckdb::Value create_value_from_uuid(JNIEnv *env, jobject param) {
90+
jlong most_significant = env->CallLongMethod(param, J_UUID_getMostSignificantBits);
91+
// Account for the following logic in UUID::FromString:
92+
// Flip the first bit to make `order by uuid` same as `order by uuid::varchar`
93+
most_significant ^= (std::numeric_limits<int64_t>::min)();
94+
jlong least_significant = env->CallLongMethod(param, J_UUID_getLeastSignificantBits);
95+
duckdb::hugeint_t hi = duckdb::hugeint_t(most_significant, least_significant);
96+
return duckdb::Value::UUID(std::move(hi));
97+
}
98+
99+
static duckdb::Value create_value_from_map(JNIEnv *env, jobject param, duckdb::ClientContext &context) {
100+
auto typeName = jstring_to_string(env, (jstring)env->CallObjectMethod(param, J_DuckMap_getSQLTypeName));
101+
102+
duckdb::LogicalType type;
103+
context.RunFunctionInTransaction([&]() { type = duckdb::TransformStringToLogicalType(typeName, context); });
104+
105+
auto entrySet = env->CallObjectMethod(param, J_Map_entrySet);
106+
auto iterator = env->CallObjectMethod(entrySet, J_Set_iterator);
107+
duckdb::vector<duckdb::Value> entries;
108+
while (env->CallBooleanMethod(iterator, J_Iterator_hasNext)) {
109+
auto entry = env->CallObjectMethod(iterator, J_Iterator_next);
110+
111+
auto key = env->CallObjectMethod(entry, J_Entry_getKey);
112+
auto value = env->CallObjectMethod(entry, J_Entry_getValue);
113+
D_ASSERT(key);
114+
D_ASSERT(value);
115+
116+
entries.push_back(duckdb::Value::STRUCT(
117+
{{"key", to_duckdb_value(env, key, context)}, {"value", to_duckdb_value(env, value, context)}}));
118+
}
119+
120+
return duckdb::Value::MAP(duckdb::ListType::GetChildType(type), entries);
121+
}
122+
123+
static duckdb::Value create_value_from_struct(JNIEnv *env, jobject param, duckdb::ClientContext &context) {
124+
auto typeName = jstring_to_string(env, (jstring)env->CallObjectMethod(param, J_Struct_getSQLTypeName));
125+
126+
duckdb::LogicalType type;
127+
context.RunFunctionInTransaction([&]() { type = TransformStringToLogicalType(typeName, context); });
128+
129+
auto jvalues = (jobjectArray)env->CallObjectMethod(param, J_Struct_getAttributes);
130+
131+
int size = env->GetArrayLength(jvalues);
132+
133+
duckdb::child_list_t<duckdb::Value> values;
134+
135+
for (int i = 0; i < size; i++) {
136+
auto name = duckdb::StructType::GetChildName(type, i);
137+
138+
auto value = env->GetObjectArrayElement(jvalues, i);
139+
140+
values.emplace_back(name, to_duckdb_value(env, value, context));
141+
}
142+
143+
return duckdb::Value::STRUCT(std::move(values));
144+
}
145+
146+
static duckdb::Value create_value_from_array(JNIEnv *env, jobject param, duckdb::ClientContext &context) {
147+
auto typeName = jstring_to_string(env, (jstring)env->CallObjectMethod(param, J_Array_getBaseTypeName));
148+
auto jvalues = (jobjectArray)env->CallObjectMethod(param, J_Array_getArray);
149+
int size = env->GetArrayLength(jvalues);
150+
151+
duckdb::LogicalType type;
152+
context.RunFunctionInTransaction([&]() { type = TransformStringToLogicalType(typeName, context); });
153+
154+
duckdb::vector<duckdb::Value> values;
155+
for (int i = 0; i < size; i++) {
156+
auto value = env->GetObjectArrayElement(jvalues, i);
157+
158+
values.emplace_back(to_duckdb_value(env, value, context));
159+
}
160+
161+
return (duckdb::Value::LIST(type, values));
162+
}
163+
164+
duckdb::Value to_duckdb_value(JNIEnv *env, jobject param, duckdb::ClientContext &context) {
165+
param = env->CallStaticObjectMethod(J_Timestamp, J_Timestamp_valueOf, param);
166+
167+
if (param == nullptr) {
168+
return (duckdb::Value());
169+
} else if (env->IsInstanceOf(param, J_Bool)) {
170+
return (duckdb::Value::BOOLEAN(env->CallBooleanMethod(param, J_Bool_booleanValue)));
171+
} else if (env->IsInstanceOf(param, J_Byte)) {
172+
return (duckdb::Value::TINYINT(env->CallByteMethod(param, J_Byte_byteValue)));
173+
} else if (env->IsInstanceOf(param, J_Short)) {
174+
return (duckdb::Value::SMALLINT(env->CallShortMethod(param, J_Short_shortValue)));
175+
} else if (env->IsInstanceOf(param, J_Int)) {
176+
return (duckdb::Value::INTEGER(env->CallIntMethod(param, J_Int_intValue)));
177+
} else if (env->IsInstanceOf(param, J_Long)) {
178+
return (duckdb::Value::BIGINT(env->CallLongMethod(param, J_Long_longValue)));
179+
} else if (env->IsInstanceOf(param, J_HugeInt)) {
180+
return create_value_from_hugeint(env, param);
181+
} else if (env->IsInstanceOf(param, J_TimestampTZ)) { // Check for subclass before superclass!
182+
return (duckdb::Value::TIMESTAMPTZ(
183+
(duckdb::timestamp_tz_t)env->CallLongMethod(param, J_TimestampTZ_getMicrosEpoch)));
184+
} else if (env->IsInstanceOf(param, J_DuckDBDate)) {
185+
return (duckdb::Value::DATE((duckdb::date_t)env->CallLongMethod(param, J_DuckDBDate_getDaysSinceEpoch)));
186+
} else if (env->IsInstanceOf(param, J_DuckDBTime)) {
187+
return (duckdb::Value::TIME((duckdb::dtime_t)env->CallLongMethod(param, J_Timestamp_getMicrosEpoch)));
188+
} else if (env->IsInstanceOf(param, J_Timestamp)) {
189+
return (duckdb::Value::TIMESTAMP((duckdb::timestamp_t)env->CallLongMethod(param, J_Timestamp_getMicrosEpoch)));
190+
} else if (env->IsInstanceOf(param, J_Float)) {
191+
return (duckdb::Value::FLOAT(env->CallFloatMethod(param, J_Float_floatValue)));
192+
} else if (env->IsInstanceOf(param, J_Double)) {
193+
return (duckdb::Value::DOUBLE(env->CallDoubleMethod(param, J_Double_doubleValue)));
194+
} else if (env->IsInstanceOf(param, J_BigDecimal)) {
195+
return create_value_from_bigdecimal(env, param);
196+
} else if (env->IsInstanceOf(param, J_String)) {
197+
auto param_string = jstring_to_string(env, (jstring)param);
198+
return (duckdb::Value(param_string));
199+
} else if (env->IsInstanceOf(param, J_ByteArray)) {
200+
return (duckdb::Value::BLOB_RAW(byte_array_to_string(env, (jbyteArray)param)));
201+
} else if (env->IsInstanceOf(param, J_UUID)) {
202+
return create_value_from_uuid(env, param);
203+
} else if (env->IsInstanceOf(param, J_DuckMap)) {
204+
return create_value_from_map(env, param, context);
205+
} else if (env->IsInstanceOf(param, J_Struct)) {
206+
return create_value_from_struct(env, param, context);
207+
} else if (env->IsInstanceOf(param, J_Array)) {
208+
return create_value_from_array(env, param, context);
209+
} else {
210+
throw duckdb::InvalidInputException("Unsupported parameter type");
211+
}
212+
}

0 commit comments

Comments
 (0)