Skip to content

Commit 765c131

Browse files
committed
VARIANT columns and parameters support (1.5)
This is a backport of the PR duckdb#574 to `v1.5-variegata` stable branch. This PR adds support for `VARIANT` columns and query parameters. Underlying values of the `VARIANT` result columns are returned from `ResultSet#getObject(col)` calls.
1 parent 391565a commit 765c131

File tree

7 files changed

+316
-7
lines changed

7 files changed

+316
-7
lines changed

src/jni/duckdb_java.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ extern "C" {
77
#include "duckdb/common/arrow/result_arrow_wrapper.hpp"
88
#include "duckdb/common/operator/cast_operators.hpp"
99
#include "duckdb/common/shared_ptr.hpp"
10+
#include "duckdb/function/scalar/variant_utils.hpp"
1011
#include "duckdb/function/table/arrow.hpp"
1112
#include "duckdb/main/appender.hpp"
1213
#include "duckdb/main/client_context.hpp"
@@ -685,6 +686,24 @@ jobject ProcessVector(JNIEnv *env, Connection *conn_ref, Vector &vec, idx_t row_
685686
}
686687
break;
687688
}
689+
case LogicalTypeId::VARIANT: {
690+
RecursiveUnifiedVectorFormat format;
691+
Vector::RecursiveToUnifiedFormat(vec, 1, format);
692+
UnifiedVariantVectorData vector_data(format);
693+
varlen_data = env->NewObjectArray(row_count, J_Object, nullptr);
694+
for (idx_t row_idx = 0; row_idx < row_count; row_idx++) {
695+
auto variant_val = VariantUtils::ConvertVariantToValue(vector_data, row_idx, 0);
696+
if (variant_val.IsNull()) {
697+
continue;
698+
}
699+
Vector variant_vec(variant_val);
700+
variant_vec.Flatten(1);
701+
jobject variant_j_vec = ProcessVector(env, conn_ref, variant_vec, 1);
702+
env->CallVoidMethod(variant_j_vec, J_DuckVector_retainConstlenData);
703+
env->SetObjectArrayElement(varlen_data, row_idx, variant_j_vec);
704+
}
705+
break;
706+
}
688707
default: {
689708
Vector string_vec(LogicalType::VARCHAR);
690709
VectorOperations::Cast(*conn_ref->context, vec, string_vec, row_count);

src/jni/refs.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ jmethodID J_DuckResultSetMeta_init;
5757

5858
jclass J_DuckVector;
5959
jmethodID J_DuckVector_init;
60+
jmethodID J_DuckVector_retainConstlenData;
6061
jfieldID J_DuckVector_constlen;
6162
jfieldID J_DuckVector_varlen;
6263

@@ -270,6 +271,7 @@ void create_refs(JNIEnv *env) {
270271
J_String_getBytes = get_method_id(env, J_String, "getBytes", "(Ljava/nio/charset/Charset;)[B");
271272

272273
J_DuckVector_init = get_method_id(env, J_DuckVector, "<init>", "(Ljava/lang/String;I[Z)V");
274+
J_DuckVector_retainConstlenData = get_method_id(env, J_DuckVector, "retainConstlenData", "()V");
273275
J_DuckVector_constlen = get_field_id(env, J_DuckVector, "constlen_data", "Ljava/nio/ByteBuffer;");
274276
J_DuckVector_varlen = get_field_id(env, J_DuckVector, "varlen_data", "[Ljava/lang/Object;");
275277

src/jni/refs.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ extern jmethodID J_DuckResultSetMeta_init;
5454

5555
extern jclass J_DuckVector;
5656
extern jmethodID J_DuckVector_init;
57+
extern jmethodID J_DuckVector_retainConstlenData;
5758
extern jfieldID J_DuckVector_constlen;
5859
extern jfieldID J_DuckVector_varlen;
5960

src/main/java/org/duckdb/DuckDBColumnType.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,6 @@ public enum DuckDBColumnType {
3636
MAP,
3737
ARRAY,
3838
UNKNOWN,
39-
UNION;
39+
UNION,
40+
VARIANT;
4041
}

src/main/java/org/duckdb/DuckDBVector.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ class DuckDBVector {
5959
this.nullmask = nullmask;
6060
}
6161

62+
private void retainConstlenData() {
63+
if (null != constlen_data) {
64+
byte[] constlenBytes = new byte[constlen_data.capacity()];
65+
constlen_data.get(constlenBytes);
66+
this.constlen_data = ByteBuffer.wrap(constlenBytes);
67+
}
68+
}
69+
6270
Object getObject(int idx) throws SQLException {
6371
if (check_and_null(idx)) {
6472
return null;
@@ -121,6 +129,8 @@ Object getObject(int idx) throws SQLException {
121129
return getStruct(idx);
122130
case UNION:
123131
return getUnion(idx);
132+
case VARIANT:
133+
return getVariant(idx);
124134
default:
125135
return getLazyString(idx);
126136
}
@@ -721,4 +731,9 @@ Object getUnion(int idx) throws SQLException {
721731

722732
return attributes[1 + tag];
723733
}
734+
735+
Object getVariant(int idx) throws SQLException {
736+
DuckDBVector vec = (DuckDBVector) varlen_data[idx];
737+
return vec.getObject(0);
738+
}
724739
}

src/test/java/org/duckdb/TestDuckDBJDBC.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2243,12 +2243,12 @@ public static void main(String[] args) throws Exception {
22432243
Class<?> clazz = Class.forName("org.duckdb." + arg1);
22442244
statusCode = runTests(new String[0], clazz);
22452245
} else {
2246-
statusCode =
2247-
runTests(args, TestDuckDBJDBC.class, TestAppender.class, TestAppenderCollection.class,
2248-
TestAppenderCollection2D.class, TestAppenderComposite.class, TestSingleValueAppender.class,
2249-
TestBatch.class, TestBindings.class, TestClosure.class, TestExtensionTypes.class,
2250-
TestMetadata.class, TestNoLib.class, /* TestSpatial.class, */ TestParameterMetadata.class,
2251-
TestPrepare.class, TestResults.class, TestSessionInit.class, TestTimestamp.class);
2246+
statusCode = runTests(args, TestDuckDBJDBC.class, TestAppender.class, TestAppenderCollection.class,
2247+
TestAppenderCollection2D.class, TestAppenderComposite.class,
2248+
TestSingleValueAppender.class, TestBatch.class, TestBindings.class, TestClosure.class,
2249+
TestExtensionTypes.class, TestMetadata.class, TestNoLib.class,
2250+
/* TestSpatial.class, */ TestParameterMetadata.class, TestPrepare.class,
2251+
TestResults.class, TestSessionInit.class, TestTimestamp.class, TestVariant.class);
22522252
}
22532253
System.exit(statusCode);
22542254
}
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
package org.duckdb;
2+
3+
import static org.duckdb.TestDuckDBJDBC.JDBC_URL;
4+
import static org.duckdb.test.Assertions.*;
5+
6+
import java.math.BigDecimal;
7+
import java.math.BigInteger;
8+
import java.sql.*;
9+
import java.util.LinkedHashMap;
10+
import java.util.Map;
11+
12+
public class TestVariant {
13+
14+
public static void test_variant_varchar() throws Exception {
15+
try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement();
16+
ResultSet rs = stmt.executeQuery("SELECT 'foo'::VARCHAR::VARIANT AS col1")) {
17+
assertTrue(rs.next());
18+
assertEquals(rs.getMetaData().getColumnType(1), Types.OTHER);
19+
assertEquals(rs.getObject(1).getClass(), String.class);
20+
assertEquals(rs.getObject(1), "foo");
21+
assertFalse(rs.next());
22+
}
23+
}
24+
25+
public static void test_variant_bool() throws Exception {
26+
try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement();
27+
ResultSet rs = stmt.executeQuery("SELECT TRUE::BOOL::VARIANT AS col1")) {
28+
assertTrue(rs.next());
29+
assertEquals(rs.getObject(1).getClass(), Boolean.class);
30+
assertEquals(rs.getObject(1), true);
31+
assertFalse(rs.next());
32+
}
33+
}
34+
35+
public static void test_variant_integrals() throws Exception {
36+
try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement();
37+
ResultSet rs = stmt.executeQuery("SELECT 41::TINYINT::VARIANT AS col1"
38+
+ " UNION ALL "
39+
+ "SELECT 42::SMALLINT::VARIANT AS col1"
40+
+ " UNION ALL "
41+
+ "SELECT 43::INTEGER::VARIANT AS col1"
42+
+ " UNION ALL "
43+
+ "SELECT 44::BIGINT::VARIANT AS col1"
44+
+ " UNION ALL "
45+
+ "SELECT 45::HUGEINT::VARIANT AS col1")) {
46+
assertTrue(rs.next());
47+
assertEquals(rs.getObject(1).getClass(), Byte.class);
48+
assertEquals(rs.getObject(1), (byte) 41);
49+
assertTrue(rs.next());
50+
assertEquals(rs.getObject(1).getClass(), Short.class);
51+
assertEquals(rs.getObject(1), (short) 42);
52+
assertTrue(rs.next());
53+
assertEquals(rs.getObject(1).getClass(), Integer.class);
54+
assertEquals(rs.getObject(1), 43);
55+
assertTrue(rs.next());
56+
assertEquals(rs.getObject(1).getClass(), Long.class);
57+
assertEquals(rs.getObject(1), (long) 44);
58+
assertTrue(rs.next());
59+
assertEquals(rs.getObject(1).getClass(), BigInteger.class);
60+
assertEquals(rs.getObject(1), BigInteger.valueOf(45));
61+
assertFalse(rs.next());
62+
}
63+
}
64+
65+
public static void test_variant_floats() throws Exception {
66+
try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement();
67+
ResultSet rs = stmt.executeQuery("SELECT 41.1::FLOAT::VARIANT AS col1"
68+
+ " UNION ALL "
69+
+ "SELECT 42.2::DOUBLE::VARIANT AS col1")) {
70+
assertTrue(rs.next());
71+
assertEquals(rs.getObject(1).getClass(), Float.class);
72+
assertEquals(rs.getObject(1), (float) 41.1);
73+
assertTrue(rs.next());
74+
assertEquals(rs.getObject(1).getClass(), Double.class);
75+
assertEquals(rs.getObject(1), 42.2);
76+
assertFalse(rs.next());
77+
}
78+
}
79+
80+
public static void test_variant_decimals() throws Exception {
81+
try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement();
82+
ResultSet rs = stmt.executeQuery("SELECT 41.1::DECIMAL(8,1)::VARIANT AS col1"
83+
+ " UNION ALL "
84+
+ "SELECT 42.2::DECIMAL(38,1)::VARIANT AS col1")) {
85+
assertTrue(rs.next());
86+
assertEquals(rs.getObject(1).getClass(), BigDecimal.class);
87+
assertEquals(rs.getObject(1), BigDecimal.valueOf(41.1));
88+
// assertEquals(rs.getMetaData().getPrecision(1), 8);
89+
// assertEquals(rs.getMetaData().getScale(1), 1);
90+
assertTrue(rs.next());
91+
assertEquals(rs.getObject(1).getClass(), BigDecimal.class);
92+
assertEquals(rs.getObject(1), BigDecimal.valueOf(42.2));
93+
// assertEquals(rs.getMetaData().getPrecision(1), 38);
94+
// assertEquals(rs.getMetaData().getScale(1), 1);
95+
assertFalse(rs.next());
96+
}
97+
}
98+
99+
public static void test_variant_null() throws Exception {
100+
try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement();
101+
ResultSet rs = stmt.executeQuery("SELECT 'foo'::VARCHAR::VARIANT AS col1"
102+
+ " UNION ALL "
103+
+ "SELECT NULL::VARIANT AS col1"
104+
+ " UNION ALL "
105+
+ "SELECT 42::INTEGER::VARIANT AS col1")) {
106+
assertTrue(rs.next());
107+
assertEquals(rs.getObject(1).getClass(), String.class);
108+
assertEquals(rs.getObject(1), "foo");
109+
assertTrue(rs.next());
110+
assertEquals(rs.getObject(1), null);
111+
assertTrue(rs.wasNull());
112+
assertTrue(rs.next());
113+
assertEquals(rs.getObject(1).getClass(), Integer.class);
114+
assertEquals(rs.getObject(1), 42);
115+
assertFalse(rs.next());
116+
}
117+
}
118+
119+
public static void test_variant_query_params() throws Exception {
120+
try (Connection conn = DriverManager.getConnection(JDBC_URL);
121+
PreparedStatement ps = conn.prepareStatement("SELECT ?::VARCHAR::VARIANT AS col1"
122+
+ " UNION ALL "
123+
+ "SELECT ?::INTEGER::VARIANT AS col1")) {
124+
ps.setString(1, "foo");
125+
ps.setInt(2, 42);
126+
try (ResultSet rs = ps.executeQuery()) {
127+
assertTrue(rs.next());
128+
assertEquals(rs.getObject(1).getClass(), String.class);
129+
assertEquals(rs.getObject(1), "foo");
130+
assertTrue(rs.next());
131+
assertEquals(rs.getObject(1).getClass(), Integer.class);
132+
assertEquals(rs.getObject(1), 42);
133+
assertFalse(rs.next());
134+
}
135+
}
136+
}
137+
138+
public static void test_variant_columns() throws Exception {
139+
try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement();
140+
ResultSet rs = stmt.executeQuery("SELECT 'foo'::VARCHAR::VARIANT AS col1, 42::INTEGER::VARIANT AS col2")) {
141+
assertTrue(rs.next());
142+
assertEquals(rs.getObject(1).getClass(), String.class);
143+
assertEquals(rs.getObject(1), "foo");
144+
assertEquals(rs.getObject(2).getClass(), Integer.class);
145+
assertEquals(rs.getObject(2), 42);
146+
assertFalse(rs.next());
147+
}
148+
}
149+
150+
public static void test_variant_array() throws Exception {
151+
try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement();
152+
ResultSet rs = stmt.executeQuery("SELECT [41, 42, 43]::INTEGER[3]::VARIANT AS col1")) {
153+
assertTrue(rs.next());
154+
assertEquals(rs.getObject(1).getClass(), DuckDBArray.class);
155+
Array arrayWrapper = (Array) rs.getObject(1);
156+
Object[] array = (Object[]) arrayWrapper.getArray();
157+
assertEquals(array.length, 3);
158+
assertEquals(array[0].getClass(), Integer.class);
159+
assertEquals(array[0], 41);
160+
assertEquals(array[1].getClass(), Integer.class);
161+
assertEquals(array[1], 42);
162+
assertEquals(array[2].getClass(), Integer.class);
163+
assertEquals(array[2], 43);
164+
assertFalse(rs.next());
165+
}
166+
}
167+
168+
public static void test_variant_list() throws Exception {
169+
try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement();
170+
ResultSet rs = stmt.executeQuery("SELECT [41, 42, 43]::INTEGER[]::VARIANT AS col1")) {
171+
assertTrue(rs.next());
172+
assertEquals(rs.getObject(1).getClass(), DuckDBArray.class);
173+
Array arrayWrapper = (Array) rs.getObject(1);
174+
Object[] array = (Object[]) arrayWrapper.getArray();
175+
assertEquals(array.length, 3);
176+
assertEquals(array[0].getClass(), Integer.class);
177+
assertEquals(array[0], 41);
178+
assertEquals(array[1].getClass(), Integer.class);
179+
assertEquals(array[1], 42);
180+
assertEquals(array[2].getClass(), Integer.class);
181+
assertEquals(array[2], 43);
182+
assertFalse(rs.next());
183+
}
184+
}
185+
186+
public static void test_variant_list_of_variants() throws Exception {
187+
try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement();
188+
ResultSet rs =
189+
stmt.executeQuery("SELECT [41::VARIANT, NULL::VARIANT, 'foo'::VARIANT]::VARIANT[]::VARIANT AS col1")) {
190+
assertTrue(rs.next());
191+
assertEquals(rs.getObject(1).getClass(), DuckDBArray.class);
192+
Array arrayWrapper = (Array) rs.getObject(1);
193+
Object[] array = (Object[]) arrayWrapper.getArray();
194+
assertEquals(array.length, 3);
195+
assertEquals(array[0].getClass(), Integer.class);
196+
assertEquals(array[0], 41);
197+
assertNull(array[1]);
198+
assertEquals(array[2].getClass(), String.class);
199+
assertEquals(array[2], "foo");
200+
assertFalse(rs.next());
201+
}
202+
}
203+
204+
public static void test_variant_map() throws Exception {
205+
try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement();
206+
ResultSet rs = stmt.executeQuery("SELECT MAP {'foo': 41, 'bar': 42}::VARIANT AS col1")) {
207+
assertTrue(rs.next());
208+
assertEquals(rs.getObject(1).getClass(), DuckDBArray.class);
209+
Array arrayWrapper = (Array) rs.getObject(1);
210+
Object[] array = (Object[]) arrayWrapper.getArray();
211+
assertEquals(array.length, 2);
212+
{
213+
DuckDBStruct struct = (DuckDBStruct) array[0];
214+
Map<?, ?> map = struct.getMap();
215+
assertEquals(map.size(), 2);
216+
assertEquals(map.get("key"), "foo");
217+
assertEquals(map.get("value"), 41);
218+
}
219+
{
220+
DuckDBStruct struct = (DuckDBStruct) array[1];
221+
Map<?, ?> map = struct.getMap();
222+
assertEquals(map.size(), 2);
223+
assertEquals(map.get("key"), "bar");
224+
assertEquals(map.get("value"), 42);
225+
}
226+
assertFalse(rs.next());
227+
}
228+
}
229+
230+
public static void test_variant_struct() throws Exception {
231+
try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement();
232+
ResultSet rs = stmt.executeQuery("SELECT {'foo': 41, 'bar': 42}::VARIANT AS col1")) {
233+
assertTrue(rs.next());
234+
DuckDBStruct struct = (DuckDBStruct) rs.getObject(1);
235+
Map<?, ?> map = struct.getMap();
236+
assertEquals(map.size(), 2);
237+
assertEquals(map.get("foo"), 41);
238+
assertEquals(map.get("bar"), 42);
239+
assertFalse(rs.next());
240+
}
241+
}
242+
243+
public static void test_variant_struct_with_variant() throws Exception {
244+
try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement()) {
245+
246+
stmt.execute("CREATE TABLE tab1 (col1 INTEGER, col2 STRUCT(s1 INTEGER, s2 VARIANT))");
247+
stmt.execute("INSERT INTO tab1 VALUES(41, row(42, 43))");
248+
stmt.execute("INSERT INTO tab1 VALUES(44, row(45, 'foo'))");
249+
250+
try (ResultSet rs = stmt.executeQuery("SELECT col2 FROM tab1 ORDER BY col1")) {
251+
assertTrue(rs.next());
252+
{
253+
DuckDBStruct struct = (DuckDBStruct) rs.getObject(1);
254+
Map<?, ?> map = struct.getMap();
255+
assertEquals(map.size(), 2);
256+
assertEquals(map.get("s1"), 42);
257+
assertEquals(map.get("s2"), 43);
258+
}
259+
assertTrue(rs.next());
260+
{
261+
DuckDBStruct struct = (DuckDBStruct) rs.getObject(1);
262+
Map<?, ?> map = struct.getMap();
263+
assertEquals(map.size(), 2);
264+
assertEquals(map.get("s1"), 45);
265+
assertEquals(map.get("s2"), "foo");
266+
}
267+
assertFalse(rs.next());
268+
}
269+
}
270+
}
271+
}

0 commit comments

Comments
 (0)