diff --git a/duckdb_java.def b/duckdb_java.def index 611b3597..170448f2 100644 --- a/duckdb_java.def +++ b/duckdb_java.def @@ -60,6 +60,9 @@ Java_org_duckdb_DuckDBBindings_duckdb_1create_1struct_1type Java_org_duckdb_DuckDBBindings_duckdb_1struct_1type_1child_1count Java_org_duckdb_DuckDBBindings_duckdb_1struct_1type_1child_1name Java_org_duckdb_DuckDBBindings_duckdb_1array_1type_1array_1size +Java_org_duckdb_DuckDBBindings_duckdb_1enum_1internal_1type +Java_org_duckdb_DuckDBBindings_duckdb_1enum_1dictionary_1size +Java_org_duckdb_DuckDBBindings_duckdb_1enum_1dictionary_1value Java_org_duckdb_DuckDBBindings_duckdb_1destroy_1logical_1type Java_org_duckdb_DuckDBBindings_duckdb_1create_1vector Java_org_duckdb_DuckDBBindings_duckdb_1destroy_1vector diff --git a/duckdb_java.exp b/duckdb_java.exp index 85e06db3..71158e9e 100644 --- a/duckdb_java.exp +++ b/duckdb_java.exp @@ -57,6 +57,9 @@ _Java_org_duckdb_DuckDBBindings_duckdb_1create_1struct_1type _Java_org_duckdb_DuckDBBindings_duckdb_1struct_1type_1child_1count _Java_org_duckdb_DuckDBBindings_duckdb_1struct_1type_1child_1name _Java_org_duckdb_DuckDBBindings_duckdb_1array_1type_1array_1size +_Java_org_duckdb_DuckDBBindings_duckdb_1enum_1internal_1type +_Java_org_duckdb_DuckDBBindings_duckdb_1enum_1dictionary_1size +_Java_org_duckdb_DuckDBBindings_duckdb_1enum_1dictionary_1value _Java_org_duckdb_DuckDBBindings_duckdb_1destroy_1logical_1type _Java_org_duckdb_DuckDBBindings_duckdb_1create_1vector _Java_org_duckdb_DuckDBBindings_duckdb_1destroy_1vector diff --git a/duckdb_java.map b/duckdb_java.map index 24efd089..fd4da82e 100644 --- a/duckdb_java.map +++ b/duckdb_java.map @@ -59,6 +59,9 @@ DUCKDB_JAVA { Java_org_duckdb_DuckDBBindings_duckdb_1struct_1type_1child_1count; Java_org_duckdb_DuckDBBindings_duckdb_1struct_1type_1child_1name; Java_org_duckdb_DuckDBBindings_duckdb_1array_1type_1array_1size; + Java_org_duckdb_DuckDBBindings_duckdb_1enum_1internal_1type; + Java_org_duckdb_DuckDBBindings_duckdb_1enum_1dictionary_1size; + Java_org_duckdb_DuckDBBindings_duckdb_1enum_1dictionary_1value; Java_org_duckdb_DuckDBBindings_duckdb_1destroy_1logical_1type; Java_org_duckdb_DuckDBBindings_duckdb_1create_1vector; Java_org_duckdb_DuckDBBindings_duckdb_1destroy_1vector; diff --git a/src/jni/bindings_logical_type.cpp b/src/jni/bindings_logical_type.cpp index fc45640c..63bc64cc 100644 --- a/src/jni/bindings_logical_type.cpp +++ b/src/jni/bindings_logical_type.cpp @@ -291,6 +291,76 @@ JNIEXPORT jlong JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1array_1type_1arra return static_cast(size); } +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_enum_internal_type + * Signature: (Ljava/nio/ByteBuffer;)I + */ +JNIEXPORT jint JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1enum_1internal_1type(JNIEnv *env, jclass, + jobject logical_type) { + + duckdb_logical_type lt = logical_type_buf_to_logical_type(env, logical_type); + if (env->ExceptionCheck()) { + return -1; + } + + duckdb_type type_id = duckdb_enum_internal_type(lt); + + return static_cast(type_id); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_enum_dictionary_size + * Signature: (Ljava/nio/ByteBuffer;)J + */ +JNIEXPORT jlong JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1enum_1dictionary_1size(JNIEnv *env, jclass, + jobject logical_type) { + + duckdb_logical_type lt = logical_type_buf_to_logical_type(env, logical_type); + if (env->ExceptionCheck()) { + return -1; + } + + idx_t size = duckdb_enum_dictionary_size(lt); + + return static_cast(size); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_enum_dictionary_value + * Signature: (Ljava/nio/ByteBuffer;J)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1enum_1dictionary_1value(JNIEnv *env, jclass, + jobject logical_type, + jlong index) { + + duckdb_logical_type lt = logical_type_buf_to_logical_type(env, logical_type); + if (env->ExceptionCheck()) { + return nullptr; + } + idx_t index_idx = jlong_to_idx(env, index); + if (env->ExceptionCheck()) { + return nullptr; + } + + idx_t size = duckdb_enum_dictionary_size(lt); + if (index_idx >= size) { + env->ThrowNew(J_SQLException, "Invalid enum field index specified"); + return nullptr; + } + + auto name_ptr = varchar_ptr(duckdb_enum_dictionary_value(lt, index_idx), varchar_deleter); + if (name_ptr.get() == nullptr) { + return nullptr; + } + + idx_t len = static_cast(std::strlen(name_ptr.get())); + + return make_jbyteArray(env, name_ptr.get(), len); +} + /* * Class: org_duckdb_DuckDBBindings * Method: duckdb_destroy_logical_type diff --git a/src/main/java/org/duckdb/DuckDBAppender.java b/src/main/java/org/duckdb/DuckDBAppender.java index 2ca7819d..ac300563 100644 --- a/src/main/java/org/duckdb/DuckDBAppender.java +++ b/src/main/java/org/duckdb/DuckDBAppender.java @@ -58,6 +58,7 @@ public class DuckDBAppender implements AutoCloseable { supportedTypes.add(DUCKDB_TYPE_STRUCT.typeId); supportedTypes.add(DUCKDB_TYPE_UNION.typeId); + supportedTypes.add(DUCKDB_TYPE_ENUM.typeId); } private static final CAPIType[] int8Types = new CAPIType[] {DUCKDB_TYPE_TINYINT, DUCKDB_TYPE_UTINYINT}; private static final CAPIType[] int16Types = new CAPIType[] {DUCKDB_TYPE_SMALLINT, DUCKDB_TYPE_USMALLINT}; @@ -69,6 +70,8 @@ public class DuckDBAppender implements AutoCloseable { private static final CAPIType[] timestampMicrosTypes = new CAPIType[] {DUCKDB_TYPE_TIMESTAMP, DUCKDB_TYPE_TIMESTAMP_TZ}; private static final CAPIType[] collectionTypes = new CAPIType[] {DUCKDB_TYPE_ARRAY, DUCKDB_TYPE_LIST}; + private static final CAPIType[] varlenTypes = new CAPIType[] {DUCKDB_TYPE_VARCHAR, DUCKDB_TYPE_BLOB}; + private static final CAPIType[] varcharOrEnumTypes = new CAPIType[] {DUCKDB_TYPE_VARCHAR, DUCKDB_TYPE_ENUM}; private static final int STRING_MAX_INLINE_BYTES = 12; @@ -564,7 +567,7 @@ public DuckDBAppender appendByteArray(byte[][] values, boolean[][] nullMask) thr } public DuckDBAppender append(byte[] values) throws SQLException { - Column col = currentColumn(DUCKDB_TYPE_BLOB); + Column col = currentColumn(varlenTypes); if (values == null) { return appendNull(); } @@ -740,13 +743,24 @@ public DuckDBAppender append(double[][] values, boolean[][] nullMask) throws SQL // append objects public DuckDBAppender append(String value) throws SQLException { - Column col = currentColumn(DUCKDB_TYPE_VARCHAR); + Column col = currentColumn(varcharOrEnumTypes); if (value == null) { return appendNull(); } - byte[] bytes = value.getBytes(UTF_8); - putStringOrBlob(col, rowIdx, bytes); + switch (col.colType) { + case DUCKDB_TYPE_VARCHAR: { + byte[] bytes = value.getBytes(UTF_8); + putStringOrBlob(col, rowIdx, bytes); + break; + } + case DUCKDB_TYPE_ENUM: { + putEnum(col, rowIdx, value); + break; + } + default: + throw new SQLException(createErrMsg("Invalid type: " + col.colType)); + } moveToNextColumn(); return this; @@ -1842,6 +1856,16 @@ private void putCompositeElement(Column col, long vectorIdx, Object value) throw putStringOrBlob(col, vectorIdx, bytes); break; } + case DUCKDB_TYPE_ENUM: { + String st = (String) value; + putEnum(col, vectorIdx, st); + break; + } + case DUCKDB_TYPE_BLOB: { + byte[] bytes = (byte[]) (value); + putStringOrBlob(col, vectorIdx, bytes); + break; + } case DUCKDB_TYPE_UUID: { UUID uid = (UUID) value; long mostSigBits = uid.getMostSignificantBits(); @@ -2058,6 +2082,31 @@ private Column putUnionTag(Column col, long vectorIdx, String tag) throws SQLExc return col.children.get(fieldWithTag); } + private void putEnum(Column col, long vectorIdx, String value) throws SQLException { + Integer numValueNullable = col.enumDict.get(value); + if (null == numValueNullable) { + throw new SQLException(createErrMsg("invalid ENUM value specified: '" + value + + "', expected one of: " + col.enumDict.keySet())); + } + + int pos = (int) (vectorIdx * col.enumInternalType.widthBytes); + col.data.position(pos); + + switch (col.enumInternalType) { + case DUCKDB_TYPE_UTINYINT: + col.data.put(numValueNullable.byteValue()); + return; + case DUCKDB_TYPE_USMALLINT: + col.data.putShort(numValueNullable.shortValue()); + return; + case DUCKDB_TYPE_UINTEGER: + col.data.putInt(numValueNullable.intValue()); + return; + default: + throw new SQLException(createErrMsg("invalid ENUM internal type: " + col.enumInternalType)); + } + } + // state invariants private boolean rowBegunInvariant() { @@ -2254,6 +2303,17 @@ private static List createTopLevelColumns(ByteBuffer chunkRef, ByteBuffe return columns; } + private static Map readEnumDict(ByteBuffer colTypeRef) { + Map dict = new LinkedHashMap<>(); + long size = duckdb_enum_dictionary_size(colTypeRef); + for (long i = 0; i < size; i++) { + byte[] nameUtf8 = duckdb_enum_dictionary_value(colTypeRef, i); + String name = strFromUTF8(nameUtf8); + dict.put(name, (int) i); + } + return dict; + } + private static class Column { private final Column parent; private final int idx; @@ -2264,6 +2324,8 @@ private static class Column { private final int decimalScale; private final long arraySize; private final String structFieldName; + private final Map enumDict; + private final CAPIType enumInternalType; private final ByteBuffer vectorRef; private final List children = new ArrayList<>(); @@ -2323,8 +2385,17 @@ private Column(Column parent, int idx, ByteBuffer colTypeRef, ByteBuffer vector, this.arraySize = duckdb_array_type_array_size(parent.colTypeRef); } + if (colType == DUCKDB_TYPE_ENUM) { + this.enumDict = readEnumDict(this.colTypeRef); + int enumInternalTypeId = duckdb_enum_internal_type(this.colTypeRef); + this.enumInternalType = capiTypeFromTypeId(enumInternalTypeId); + } else { + this.enumDict = null; + this.enumInternalType = null; + } + long maxElems = maxElementsCount(); - if (colType.widthBytes > 0 || colType == DUCKDB_TYPE_DECIMAL) { + if (colType.widthBytes > 0 || colType == DUCKDB_TYPE_DECIMAL || colType == DUCKDB_TYPE_ENUM) { long vectorSizeBytes = maxElems * widthBytes(); this.data = duckdb_vector_get_data(vectorRef, vectorSizeBytes); if (null == this.data) { @@ -2423,6 +2494,8 @@ void setNullOnVectorIdx(long vectorIdx) { long widthBytes() { if (colType == DUCKDB_TYPE_DECIMAL) { return decimalInternalType.widthBytes; + } else if (colType == DUCKDB_TYPE_ENUM) { + return enumInternalType.widthBytes; } else { return colType.widthBytes; } diff --git a/src/main/java/org/duckdb/DuckDBBindings.java b/src/main/java/org/duckdb/DuckDBBindings.java index df0674c4..4ee45c04 100644 --- a/src/main/java/org/duckdb/DuckDBBindings.java +++ b/src/main/java/org/duckdb/DuckDBBindings.java @@ -43,6 +43,12 @@ public class DuckDBBindings { static native void duckdb_destroy_logical_type(ByteBuffer logical_type); + static native int duckdb_enum_internal_type(ByteBuffer logical_type); + + static native long duckdb_enum_dictionary_size(ByteBuffer logical_type); + + static native byte[] duckdb_enum_dictionary_value(ByteBuffer logical_type, long index); + // vector static native ByteBuffer duckdb_create_vector(ByteBuffer logical_type); @@ -163,7 +169,7 @@ enum CAPIType { // duckdb_timestamp_ns (nanoseconds) DUCKDB_TYPE_TIMESTAMP_NS(22, 8), // enum type, only useful as logical type - DUCKDB_TYPE_ENUM(23), + DUCKDB_TYPE_ENUM(23, 0), // list type, only useful as logical type DUCKDB_TYPE_LIST(24, 16), // struct type, only useful as logical type diff --git a/src/test/java/org/duckdb/TestAppender.java b/src/test/java/org/duckdb/TestAppender.java index 598cceee..a67840cb 100644 --- a/src/test/java/org/duckdb/TestAppender.java +++ b/src/test/java/org/duckdb/TestAppender.java @@ -1,5 +1,6 @@ package org.duckdb; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.time.ZoneOffset.UTC; import static org.duckdb.DuckDBHugeInt.HUGE_INT_MAX; import static org.duckdb.DuckDBHugeInt.HUGE_INT_MIN; @@ -848,4 +849,75 @@ public static void test_appender_incomplete_flush() throws Exception { } } } + + public static void test_appender_varchar_as_bytes() throws Exception { + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tab1 (col1 INTEGER, col2 VARCHAR)"); + String cjkValue = "\u4B54\uD86D\uDF7C\uD83D\uDD25\uD83D\uDE1C"; + + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(41) + .append("foo".getBytes(UTF_8)) + .endRow() + .beginRow() + .append(42) + .append(cjkValue.getBytes(UTF_8)) + .endRow(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT col2 FROM tab1 ORDER BY col1")) { + assertTrue(rs.next()); + assertEquals(rs.getString(1), "foo"); + + assertTrue(rs.next()); + assertEquals(rs.getString(1), cjkValue); + + assertFalse(rs.next()); + } + } + } + + public static void test_appender_basic_enum() throws Exception { + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + + stmt.execute("CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');"); + stmt.execute("CREATE TABLE tab1(col1 INTEGER, col2 mood)"); + + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow().append(41).append("sad").endRow(); + appender.beginRow().append(42).append("happy").endRow(); + appender.beginRow().append(43).appendDefault().endRow(); + appender.beginRow().append(44).appendNull().endRow(); + appender.beginRow().append(45).append("ok").endRow(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT CAST(col2 AS VARCHAR) FROM tab1 ORDER BY col1")) { + assertTrue(rs.next()); + assertEquals(rs.getString(1), "sad"); + + assertTrue(rs.next()); + assertEquals(rs.getString(1), "happy"); + + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + + assertTrue(rs.next()); + assertEquals(rs.getString(1), "ok"); + + assertFalse(rs.next()); + } + + try (DuckDBAppender appender = conn.createAppender("tab1")) { + assertThrows(() -> { appender.beginRow().append(44).append("foobar").endRow(); }, SQLException.class); + } + } + } } diff --git a/src/test/java/org/duckdb/TestAppenderCollection.java b/src/test/java/org/duckdb/TestAppenderCollection.java index a6e38aac..a67a2757 100644 --- a/src/test/java/org/duckdb/TestAppenderCollection.java +++ b/src/test/java/org/duckdb/TestAppenderCollection.java @@ -1,11 +1,13 @@ package org.duckdb; +import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import static org.duckdb.TestDuckDBJDBC.JDBC_URL; import static org.duckdb.test.Assertions.*; import static org.duckdb.test.Assertions.assertFalse; import static org.duckdb.test.Helpers.createMap; +import java.nio.charset.StandardCharsets; import java.sql.*; import java.time.*; import java.util.*; @@ -1462,4 +1464,110 @@ public static void test_appender_list_bigint() throws Exception { } } } + + public static void test_appender_list_enum() throws Exception { + int count = 1 << 12; // auto flush twice + int tail = 7; // flushed on close + int listLen = (1 << 6) + 7; // increase this for stress tests + + List enumKeys = asList("sad", "ok", "happy"); + + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');"); + stmt.execute("CREATE TABLE tab1(col1 INTEGER, col2 mood[])"); + + try (DuckDBAppender appender = conn.createAppender("tab1")) { + for (int i = 0; i < count + tail; i++) { + List list = new ArrayList<>(); + for (int j = 0; j < Math.min(i, listLen); j++) { + if (0 == (i + j) % 13) { + list.add(null); + } else { + String key = enumKeys.get((i + j) % enumKeys.size()); + list.add(key); + } + } + appender.beginRow().append(i).append(list).endRow(); + } + } + + try (ResultSet rs = stmt.executeQuery("SELECT count(*) FROM tab1")) { + assertTrue(rs.next()); + assertEquals(rs.getInt(1), count + tail); + assertFalse(rs.next()); + } + + try (ResultSet rs = stmt.executeQuery( + "SELECT count(*) FROM (SELECT unnest(col2) FROM tab1 WHERE col1 = " + (listLen - 7) + ")")) { + assertTrue(rs.next()); + assertEquals(rs.getInt(1), listLen - 7); + assertFalse(rs.next()); + } + + try (ResultSet rs = stmt.executeQuery("SELECT col1, unnest(col2) FROM tab1 ORDER BY col1")) { + for (int i = 0; i < count + tail; i++) { + for (int j = 0; j < Math.min(i, listLen); j++) { + assertTrue(rs.next()); + assertEquals(rs.getInt(1), i); + if (0 == (i + j) % 13) { + assertNull(rs.getObject(2)); + assertTrue(rs.wasNull()); + } else { + String expected = enumKeys.get((i + j) % enumKeys.size()); + assertEquals(rs.getString(2), expected); + } + } + } + } + } + } + + public static void test_appender_list_basic_blob() throws Exception { + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + + stmt.execute("CREATE TABLE tab1(col1 INT, col2 BLOB[])"); + + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList("foo".getBytes(UTF_8), "barbazboo0123456789".getBytes(UTF_8), "bar".getBytes(UTF_8))) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, "boo".getBytes(UTF_8))) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertEquals(rs.getBytes(1), "foo".getBytes(UTF_8)); + assertTrue(rs.next()); + assertEquals(rs.getBytes(1), "barbazboo0123456789".getBytes(UTF_8)); + assertTrue(rs.next()); + assertEquals(rs.getBytes(1), "bar".getBytes(UTF_8)); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertEquals(rs.getBytes(1), "boo".getBytes(UTF_8)); + assertFalse(rs.next()); + } + } + } } diff --git a/src/test/java/org/duckdb/TestAppenderComposite.java b/src/test/java/org/duckdb/TestAppenderComposite.java index 5930b7b4..d063e5f0 100644 --- a/src/test/java/org/duckdb/TestAppenderComposite.java +++ b/src/test/java/org/duckdb/TestAppenderComposite.java @@ -882,4 +882,57 @@ public static void test_appender_list_basic_map() throws Exception { } } } + + public static void test_appender_list_basic_struct_enum() throws Exception { + Collection struct1 = asList(42, "sad"); + Collection struct2 = asList(null, "ok"); + Collection struct3 = asList(43, null); + Collection struct4 = asList(44, "happy"); + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + + stmt.execute("CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');"); + stmt.execute("CREATE TABLE tab1(col1 INT, col2 STRUCT(s1 INT, s2 mood)[])"); + + try (DuckDBAppender appender = conn.createAppender("tab1")) { + appender.beginRow() + .append(42) + .append(asList(struct1, struct2, struct3)) + .endRow() + .beginRow() + .append(43) + .append((List) null) + .endRow() + .beginRow() + .append(44) + .append(asList(null, struct4)) + .endRow() + .flush(); + } + + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 42")) { + assertTrue(rs.next()); + assertFetchedStructEquals(rs.getObject(1), struct1); + assertTrue(rs.next()); + assertFetchedStructEquals(rs.getObject(1), struct2); + assertTrue(rs.next()); + assertFetchedStructEquals(rs.getObject(1), struct3); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT col2 from tab1 WHERE col1 = 43")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + } + try (ResultSet rs = stmt.executeQuery("SELECT unnest(col2) from tab1 WHERE col1 = 44")) { + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertTrue(rs.next()); + assertFetchedStructEquals(rs.getObject(1), struct4); + assertFalse(rs.next()); + } + } + } } diff --git a/src/test/java/org/duckdb/TestBindings.java b/src/test/java/org/duckdb/TestBindings.java index cc6ef733..2c97dfee 100644 --- a/src/test/java/org/duckdb/TestBindings.java +++ b/src/test/java/org/duckdb/TestBindings.java @@ -342,4 +342,30 @@ public static void test_bindings_decimal_type() throws Exception { assertEquals(duckdb_appender_destroy(appender), 0); } } + + public static void test_bindings_enum_type() throws Exception { + try (DuckDBConnection conn = DriverManager.getConnection(JDBC_URL).unwrap(DuckDBConnection.class); + Statement stmt = conn.createStatement()) { + + stmt.execute("CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');"); + stmt.execute("CREATE TABLE tab1(col1 mood)"); + + ByteBuffer[] out = new ByteBuffer[1]; + int state = + duckdb_appender_create_ext(conn.connRef, "memory".getBytes(UTF_8), null, "tab1".getBytes(UTF_8), out); + assertEquals(state, 0); + ByteBuffer appender = out[0]; + assertNotNull(appender); + + ByteBuffer enumType = duckdb_appender_column_type(appender, 0); + assertEquals(duckdb_enum_internal_type(enumType), DUCKDB_TYPE_UTINYINT.typeId); + assertEquals(duckdb_enum_dictionary_size(enumType), 3L); + assertEquals(duckdb_enum_dictionary_value(enumType, 0), "sad".getBytes(UTF_8)); + assertEquals(duckdb_enum_dictionary_value(enumType, 1), "ok".getBytes(UTF_8)); + assertEquals(duckdb_enum_dictionary_value(enumType, 2), "happy".getBytes(UTF_8)); + + assertThrows(() -> { duckdb_enum_dictionary_value(enumType, 3); }, SQLException.class); + assertThrows(() -> { duckdb_enum_dictionary_value(enumType, -1); }, SQLException.class); + } + } }