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
3 changes: 3 additions & 0 deletions duckdb_java.def
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions duckdb_java.exp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions duckdb_java.map
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
70 changes: 70 additions & 0 deletions src/jni/bindings_logical_type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,76 @@ JNIEXPORT jlong JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1array_1type_1arra
return static_cast<jlong>(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<jint>(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<jlong>(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<idx_t>(std::strlen(name_ptr.get()));

return make_jbyteArray(env, name_ptr.get(), len);
}

/*
* Class: org_duckdb_DuckDBBindings
* Method: duckdb_destroy_logical_type
Expand Down
83 changes: 78 additions & 5 deletions src/main/java/org/duckdb/DuckDBAppender.java
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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;

Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -2254,6 +2303,17 @@ private static List<Column> createTopLevelColumns(ByteBuffer chunkRef, ByteBuffe
return columns;
}

private static Map<String, Integer> readEnumDict(ByteBuffer colTypeRef) {
Map<String, Integer> 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;
Expand All @@ -2264,6 +2324,8 @@ private static class Column {
private final int decimalScale;
private final long arraySize;
private final String structFieldName;
private final Map<String, Integer> enumDict;
private final CAPIType enumInternalType;

private final ByteBuffer vectorRef;
private final List<Column> children = new ArrayList<>();
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/org/duckdb/DuckDBBindings.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
72 changes: 72 additions & 0 deletions src/test/java/org/duckdb/TestAppender.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
}
}
}
}
Loading