Skip to content

Commit 97bdc34

Browse files
committed
Support spatial extension types
This PR changes the handling of extension types. Instead of treating all custom types (detected by having an alias) as `VARCHAR`, it ignores type alias and uses underlying logical type. This allows to work with the following types from the spatial extension using default JDBC types `java.sql.Struct`, `java.sql.Array`, and `java.sql.Blob`: - `POINT_2D`: as `STRUCT{x: DOUBLE, y: DOUBLE}` - `POINT_3D`: as `STRUCT{x: DOUBLE, y: DOUBLE, z: DOUBLE}` - `POINT_4D`: as `STRUCT{x: DOUBLE, y: DOUBLE, z: DOUBLE, m: DOUBLE}` - `LINESTRING_2D`: as `LIST[STRUCT{x: DOUBLE, y: DOUBLE}]` - `POLYGON_2D`: as `LIST[LIST[STRUCT{x: DOUBLE, y: DOUBLE}]]` - `BOX_2D`: as `STRUCT{min_x: DOUBLE, max_x: DOUBLE, min_y: DOUBLE, max_y: DOUBLE}` - `BOX_2DF`: as `STRUCT{min_x: FLOAT, max_x: FLOAT, min_y: FLOAT, max_y: FLOAT}` - `GEOMETRY`: as `BLOB` - `WKB_BLOB`: as `BLOB` Special handling is left only for `JSON` type because it is mapped to driver-specific `JsonNode` class. Testing: existing test for extnsion types is adjusted to use `java.sql.Struct` and `java.sql.Blob`. New test is added to cover all spatial types in bind parameters and in result set, minor enhancements are added to tests runner. Fixes: duckdb#37
1 parent 955c3a0 commit 97bdc34

File tree

9 files changed

+454
-76
lines changed

9 files changed

+454
-76
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,7 @@ add_library(duckdb_java SHARED
560560
src/jni/duckdb_java.cpp
561561
src/jni/functions.cpp
562562
src/jni/refs.cpp
563+
src/jni/types.cpp
563564
src/jni/util.cpp
564565
${DUCKDB_SRC_FILES})
565566

src/jni/duckdb_java.cpp

Lines changed: 3 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "duckdb/parser/parsed_data/create_type_info.hpp"
1515
#include "functions.hpp"
1616
#include "refs.hpp"
17+
#include "types.hpp"
1718
#include "util.hpp"
1819

1920
#include <cstdint>
@@ -396,42 +397,6 @@ void _duckdb_jdbc_free_result(JNIEnv *env, jclass, jobject res_ref_buf) {
396397
}
397398
}
398399

399-
static std::string type_to_jduckdb_type(LogicalType logical_type) {
400-
switch (logical_type.id()) {
401-
case LogicalTypeId::DECIMAL: {
402-
403-
uint8_t width = 0;
404-
uint8_t scale = 0;
405-
logical_type.GetDecimalProperties(width, scale);
406-
std::string width_scale = std::to_string(width) + std::string(";") + std::to_string(scale);
407-
408-
auto physical_type = logical_type.InternalType();
409-
switch (physical_type) {
410-
case PhysicalType::INT16: {
411-
string res = std::string("DECIMAL16;") + width_scale;
412-
return res;
413-
}
414-
case PhysicalType::INT32: {
415-
string res = std::string("DECIMAL32;") + width_scale;
416-
return res;
417-
}
418-
case PhysicalType::INT64: {
419-
string res = std::string("DECIMAL64;") + width_scale;
420-
return res;
421-
}
422-
case PhysicalType::INT128: {
423-
string res = std::string("DECIMAL128;") + width_scale;
424-
return res;
425-
}
426-
default:
427-
return std::string("no physical type found");
428-
}
429-
} break;
430-
default:
431-
return logical_type.ToString();
432-
}
433-
}
434-
435400
static jobject build_meta(JNIEnv *env, size_t column_count, size_t n_param, const duckdb::vector<string> &names,
436401
const duckdb::vector<LogicalType> &types, StatementProperties properties) {
437402
auto name_array = env->NewObjectArray(column_count, J_String, nullptr);
@@ -515,6 +480,7 @@ jobjectArray _duckdb_jdbc_fetch(JNIEnv *env, jclass, jobject res_ref_buf, jobjec
515480

516481
return vec_array;
517482
}
483+
518484
jobject ProcessVector(JNIEnv *env, Connection *conn_ref, Vector &vec, idx_t row_count) {
519485
auto type_str = env->NewStringUTF(type_to_jduckdb_type(vec.GetType()).c_str());
520486
// construct nullmask
@@ -530,11 +496,7 @@ jobject ProcessVector(JNIEnv *env, Connection *conn_ref, Vector &vec, idx_t row_
530496
jobject constlen_data = nullptr;
531497
jobjectArray varlen_data = nullptr;
532498

533-
// this allows us to treat aliased (usually extension) types as strings
534-
auto type = vec.GetType();
535-
auto type_id = type.HasAlias() ? LogicalTypeId::UNKNOWN : type.id();
536-
537-
switch (type_id) {
499+
switch (vec.GetType().id()) {
538500
case LogicalTypeId::BOOLEAN:
539501
constlen_data = env->NewDirectByteBuffer(FlatVector::GetData(vec), row_count * sizeof(bool));
540502
break;

src/jni/types.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#include "types.hpp"
2+
3+
#include <string>
4+
#include <vector>
5+
6+
std::string type_to_jduckdb_type(duckdb::LogicalType logical_type) {
7+
switch (logical_type.id()) {
8+
case duckdb::LogicalTypeId::DECIMAL: {
9+
10+
uint8_t width = 0;
11+
uint8_t scale = 0;
12+
logical_type.GetDecimalProperties(width, scale);
13+
std::string width_scale = std::to_string(width) + std::string(";") + std::to_string(scale);
14+
15+
auto physical_type = logical_type.InternalType();
16+
switch (physical_type) {
17+
case duckdb::PhysicalType::INT16: {
18+
std::string res = std::string("DECIMAL16;") + width_scale;
19+
return res;
20+
}
21+
case duckdb::PhysicalType::INT32: {
22+
std::string res = std::string("DECIMAL32;") + width_scale;
23+
return res;
24+
}
25+
case duckdb::PhysicalType::INT64: {
26+
std::string res = std::string("DECIMAL64;") + width_scale;
27+
return res;
28+
}
29+
case duckdb::PhysicalType::INT128: {
30+
std::string res = std::string("DECIMAL128;") + width_scale;
31+
return res;
32+
}
33+
default:
34+
return std::string("no physical type found");
35+
}
36+
} break;
37+
default:
38+
// JSON requires special handling because it is mapped
39+
// to JsonNode class
40+
if (logical_type.IsJSONType()) {
41+
return logical_type.GetAlias();
42+
}
43+
return duckdb::EnumUtil::ToString(logical_type.id());
44+
}
45+
}

src/jni/types.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#pragma once
2+
3+
#include "duckdb.hpp"
4+
5+
std::string type_to_jduckdb_type(duckdb::LogicalType logical_type);

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

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,7 @@
1111
import static org.duckdb.DuckDBDriver.DUCKDB_USER_AGENT_PROPERTY;
1212
import static org.duckdb.DuckDBDriver.JDBC_STREAM_RESULTS;
1313
import static org.duckdb.DuckDBTimestamp.localDateTimeFromTimestamp;
14-
import static org.duckdb.test.Assertions.assertEquals;
15-
import static org.duckdb.test.Assertions.assertFalse;
16-
import static org.duckdb.test.Assertions.assertNotNull;
17-
import static org.duckdb.test.Assertions.assertNull;
18-
import static org.duckdb.test.Assertions.assertThrows;
19-
import static org.duckdb.test.Assertions.assertThrowsMaybe;
20-
import static org.duckdb.test.Assertions.assertTrue;
21-
import static org.duckdb.test.Assertions.fail;
14+
import static org.duckdb.test.Assertions.*;
2215
import static org.duckdb.test.Runner.runTests;
2316

2417
import java.math.BigDecimal;
@@ -1302,7 +1295,7 @@ public static void test_lots_of_decimals() throws Exception {
13021295
conn.close();
13031296
}
13041297

1305-
public static void test_big_data() throws Exception {
1298+
public static void test_lots_of_big_data() throws Exception {
13061299
Connection conn = DriverManager.getConnection(JDBC_URL);
13071300
Statement stmt = conn.createStatement();
13081301
int rows = 10000;
@@ -4187,21 +4180,6 @@ private static Map<String, Object> structToMap(DuckDBStruct actual) throws SQLEx
41874180
return result;
41884181
}
41894182

4190-
private static <T> void assertListsEqual(List<T> actual, List<T> expected) throws Exception {
4191-
assertListsEqual(actual, expected, "");
4192-
}
4193-
4194-
private static <T> void assertListsEqual(List<T> actual, List<T> expected, String label) throws Exception {
4195-
assertEquals(actual.size(), expected.size());
4196-
4197-
ListIterator<T> itera = actual.listIterator();
4198-
ListIterator<T> itere = expected.listIterator();
4199-
4200-
while (itera.hasNext()) {
4201-
assertEquals(itera.next(), itere.next(), label);
4202-
}
4203-
}
4204-
42054183
public static void test_cancel() throws Exception {
42064184
ExecutorService service = Executors.newFixedThreadPool(1);
42074185
try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement()) {
@@ -4300,7 +4278,7 @@ public static void test_invalid_execute_calls() throws Exception {
43004278
}
43014279
}
43024280

4303-
public static void test_race() throws Exception {
4281+
public static void test_lots_of_races() throws Exception {
43044282
try (Connection connection = DriverManager.getConnection(JDBC_URL)) {
43054283
ExecutorService executorService = Executors.newFixedThreadPool(10);
43064284

@@ -4799,6 +4777,15 @@ public static void test_typed_connection_properties() throws Exception {
47994777
}
48004778

48014779
public static void main(String[] args) throws Exception {
4802-
System.exit(runTests(args, TestDuckDBJDBC.class, TestExtensionTypes.class));
4780+
String arg1 = args.length > 0 ? args[0] : "";
4781+
final int statusCode;
4782+
if (arg1.startsWith("Test")) {
4783+
Class<?> clazz = Class.forName("org.duckdb." + arg1);
4784+
statusCode = runTests(new String[0], clazz);
4785+
} else {
4786+
// extension installation fails on CI, Spatial test is temporary disabled
4787+
statusCode = runTests(args, TestDuckDBJDBC.class, TestExtensionTypes.class /*, TestSpatial.class */);
4788+
}
4789+
System.exit(statusCode);
48034790
}
48044791
}

src/test/java/org/duckdb/TestExtensionTypes.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,7 @@
33
import static org.duckdb.TestDuckDBJDBC.JDBC_URL;
44
import static org.duckdb.test.Assertions.assertEquals;
55

6-
import java.sql.Connection;
7-
import java.sql.DriverManager;
8-
import java.sql.ResultSet;
9-
import java.sql.ResultSetMetaData;
10-
import java.sql.Statement;
11-
import java.sql.Types;
6+
import java.sql.*;
127

138
public class TestExtensionTypes {
149

@@ -21,8 +16,14 @@ public static void test_extension_type() throws Exception {
2116
try (ResultSet rs = stmt.executeQuery(
2217
"SELECT {\"hello\": 'foo', \"world\": 'bar'}::test_type, '\\xAA'::byte_test_type")) {
2318
rs.next();
24-
assertEquals(rs.getObject(1), "{'hello': foo, 'world': bar}");
25-
assertEquals(rs.getObject(2), "\\xAA");
19+
Struct struct = (Struct) rs.getObject(1);
20+
Object[] attrs = struct.getAttributes();
21+
assertEquals(attrs[0], "foo");
22+
assertEquals(attrs[1], "bar");
23+
Blob blob = rs.getBlob(2);
24+
byte[] bytes = blob.getBytes(1, (int) blob.length());
25+
assertEquals(bytes.length, 1);
26+
assertEquals(bytes[0] & 0xff, 0xaa);
2627
}
2728
}
2829
}

0 commit comments

Comments
 (0)