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
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -591,12 +591,14 @@ add_library(duckdb_java SHARED
src/jni/bindings_common.cpp
src/jni/bindings_data_chunk.cpp
src/jni/bindings_logical_type.cpp
src/jni/bindings_scalar_function.cpp
src/jni/bindings_validity.cpp
src/jni/bindings_vector.cpp
src/jni/config.cpp
src/jni/duckdb_java.cpp
src/jni/functions.cpp
src/jni/refs.cpp
src/jni/scalar_functions.cpp
src/jni/types.cpp
src/jni/util.cpp
${DUCKDB_SRC_FILES})
Expand Down
2 changes: 2 additions & 0 deletions CMakeLists.txt.in
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,14 @@ add_library(duckdb_java SHARED
src/jni/bindings_common.cpp
src/jni/bindings_data_chunk.cpp
src/jni/bindings_logical_type.cpp
src/jni/bindings_scalar_function.cpp
src/jni/bindings_validity.cpp
src/jni/bindings_vector.cpp
src/jni/config.cpp
src/jni/duckdb_java.cpp
src/jni/functions.cpp
src/jni/refs.cpp
src/jni/scalar_functions.cpp
src/jni/types.cpp
src/jni/util.cpp
${DUCKDB_SRC_FILES})
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ This optionally takes an argument to only run a single test, for example:
```
java -cp "build/release/duckdb_jdbc_tests.jar:build/release/duckdb_jdbc.jar" org/duckdb/TestDuckDBJDBC test_valid_but_local_config_throws_exception
```

Scalar function usage examples: [UDF.MD](UDF.MD)
202 changes: 202 additions & 0 deletions UDF.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
# Java Scalar Functions (UDF)

Use `DuckDBFunctions.scalarFunction()` to build and register scalar functions.
`register(java.sql.Connection)` returns a `DuckDBRegisteredFunction` with metadata about the registered scalar function.

Registered functions are also tracked in a Java-side registry exposed by `DuckDBDriver.registeredFunctions()`.
This registry is bookkeeping for functions registered through the JDBC API, not an authoritative view of the DuckDB catalog.
`DuckDBDriver.clearFunctionsRegistry()` clears only the Java-side registry and does not de-register functions from DuckDB.

## Recommended API (Functional Interfaces)

Use these overloads for simple functions:

- `withFunction(Supplier<?>)` for zero arguments
- `withFunction(Function<?, ?>)` for one argument
- `withFunction(BiFunction<?, ?, ?>)` for two arguments
- `withIntFunction(IntUnaryOperator | IntBinaryOperator)` for `INTEGER` unary/binary functions
- `withLongFunction(LongUnaryOperator | LongBinaryOperator)` for `BIGINT` unary/binary functions
- `withDoubleFunction(DoubleUnaryOperator | DoubleBinaryOperator)` for `DOUBLE` unary/binary functions

### Simple example (`withIntFunction`)

```java
try (Connection conn = DriverManager.getConnection("jdbc:duckdb:")) {
DuckDBRegisteredFunction function = DuckDBFunctions.scalarFunction()
.withName("java_add_one")
.withParameter(Integer.class)
.withReturnType(Integer.class)
.withIntFunction(x -> x + 1)
.register(conn);
}
```

```sql
SELECT java_add_one(41);
```

### Slightly more complex example (`withDoubleFunction`)

```java
try (Connection conn = DriverManager.getConnection("jdbc:duckdb:")) {
DuckDBFunctions.scalarFunction()
.withName("java_weighted_sum")
.withParameter(Double.class)
.withParameter(Double.class)
.withReturnType(Double.class)
.withDoubleFunction((x, w) -> x * w + 10.0)
.register(conn);
}
```

```sql
SELECT java_weighted_sum(2.5, 4.0);
```

Behavior:

- `Function` and `BiFunction` callbacks receive `null` for NULL inputs; implement null handling in Java callback logic.
- `withIntFunction(...)`, `withLongFunction(...)`, and `withDoubleFunction(...)` run with null propagation enabled.
- For `withVectorizedFunction(...)`, use `ctx.propagateNulls(true)` when you want stream-level null skipping and automatic NULL output.
- For `Supplier`, returning `null` writes NULL output.
- `Function` and `BiFunction` are fixed arity only (no varargs).

Runtime error model:

- Callback-time reader/writer/context type and value failures throw `DuckDBFunctionException`.
- Invalid row/column indexes throw `IndexOutOfBoundsException`.
- `SQLException` remains for registration-time API usage and type declaration/validation.

## Type declaration and mapping

`withParameter(...)` and `withReturnType(...)` accept:

- `Class<?>`
- `DuckDBColumnType`
- `DuckDBLogicalType`

Common class mappings include:

- `Integer` -> `INTEGER`
- `Long` -> `BIGINT`
- `String` -> `VARCHAR`
- `BigDecimal` -> `DECIMAL`
- `BigInteger` -> `HUGEINT`
- `LocalDate` and `java.sql.Date` -> `DATE`
- `LocalDateTime`, `java.sql.Timestamp`, and `java.util.Date` -> `TIMESTAMP`

Notes:

- `UHUGEINT` is supported through explicit `DuckDBColumnType.UHUGEINT`/`DuckDBLogicalType` declarations.
- Java class auto-mapping for `BigInteger` remains `HUGEINT`.

Use `DuckDBLogicalType.decimal(width, scale)` for explicit DECIMAL precision/scale.

## Varargs

Declare varargs type with `withVarArgs(DuckDBLogicalType)`.

For functional varargs, use `withVarArgsFunction(Function<Object[], ?>)`:

```java
try (Connection conn = DriverManager.getConnection("jdbc:duckdb:");
DuckDBLogicalType intType = DuckDBLogicalType.of(DuckDBColumnType.INTEGER)) {
DuckDBFunctions.scalarFunction()
.withName("java_sum_varargs")
.withParameter(Integer.class) // fixed argument(s)
.withVarArgs(intType) // variadic argument type
.withReturnType(Integer.class)
.withVarArgsFunction(args -> {
int sum = 0;
for (Object arg : args) {
sum += (Integer) arg;
}
return sum;
})
.register(conn);
}
```

```sql
SELECT java_sum_varargs(1, 2, 3, 4);
```

Notes:

- `withFunction(Function)` and `withFunction(BiFunction)` reject varargs.
- `withVarArgsFunction(...)` requires `withVarArgs(...)` first.

## Builder methods

- `withName(String)`
- `withParameter(Class<?> | DuckDBColumnType | DuckDBLogicalType)`
- `withParameters(Class<?>...)`
- `withReturnType(Class<?> | DuckDBColumnType | DuckDBLogicalType)`
- `withFunction(Supplier | Function | BiFunction)`
- `withIntFunction(IntUnaryOperator | IntBinaryOperator)`
- `withLongFunction(LongUnaryOperator | LongBinaryOperator)`
- `withDoubleFunction(DoubleUnaryOperator | DoubleBinaryOperator)`
- `withVarArgs(DuckDBLogicalType)`
- `withVarArgsFunction(Function<Object[], ?>)`
- `withVectorizedFunction(DuckDBScalarFunction)`
- `withVolatile()`
- `withSpecialHandling()`
- `register(java.sql.Connection)`

## Registered Function Metadata And Registry

`DuckDBRegisteredFunction` exposes immutable metadata about the successful registration result:

- `name()`
- `functionKind()`
- `isScalar()`
- parameter and return type metadata
- callback and flags used at registration time

To inspect Java-side registrations:

```java
List<DuckDBRegisteredFunction> functions = DuckDBDriver.registeredFunctions();
```

The returned list is read-only. Duplicate function names may appear in the registry.

## Advanced API (`DuckDBScalarFunction`)

Use `withVectorizedFunction(...)` for full context control through `DuckDBScalarContext`.

Example with multiple input types (`TIMESTAMP`, `VARCHAR`, `DOUBLE`) and `VARCHAR` output:

```java
try (Connection conn = DriverManager.getConnection("jdbc:duckdb:");
DuckDBLogicalType tsType = DuckDBLogicalType.of(DuckDBColumnType.TIMESTAMP);
DuckDBLogicalType strType = DuckDBLogicalType.of(DuckDBColumnType.VARCHAR);
DuckDBLogicalType dblType = DuckDBLogicalType.of(DuckDBColumnType.DOUBLE)) {
DuckDBFunctions.scalarFunction()
.withName("java_event_label")
.withParameter(tsType)
.withParameter(strType)
.withParameter(dblType)
.withReturnType(strType)
.withVectorizedFunction(ctx -> {
ctx.propagateNulls(true).stream().forEachOrdered(row -> {
String value = row.getLocalDateTime(0) + " | "
+ row.getString(1).trim().toUpperCase()
+ " | " + row.getDouble(2);
row.setString(value);
});
})
.register(conn);
}
```

```sql
SELECT java_event_label(TIMESTAMP '2026-04-04 12:00:00', 'launch', 4.5);
```

Lifecycle rules:

- `DuckDBScalarContext`, `DuckDBScalarRow`, `DuckDBReadableVector`, and `DuckDBWritableVector` are valid only during callback execution.
- `DuckDBReadableVector` and `DuckDBWritableVector` are abstract callback runtime types (not interfaces).
- Write exactly one output value per input row for each callback invocation.
- With `propagateNulls(true)`, `DuckDBScalarContext.stream()` skips rows that contain NULL in any input column and writes NULL to the output for those rows.
15 changes: 14 additions & 1 deletion duckdb_java.def
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,18 @@ Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1set_1schema
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1startup

Java_org_duckdb_DuckDBBindings_duckdb_1vector_1size
Java_org_duckdb_DuckDBBindings_duckdb_1create_1scalar_1function
Java_org_duckdb_DuckDBBindings_duckdb_1destroy_1scalar_1function
Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1set_1name
Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1add_1parameter
Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1set_1return_1type
Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1set_1varargs
Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1set_1volatile
Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1set_1special_1handling
Java_org_duckdb_DuckDBBindings_duckdb_1register_1scalar_1function
Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1set_1error
Java_org_duckdb_DuckDBBindings_duckdb_1create_1logical_1type
Java_org_duckdb_DuckDBBindings_duckdb_1create_1decimal_1type
Java_org_duckdb_DuckDBBindings_duckdb_1get_1type_1id
Java_org_duckdb_DuckDBBindings_duckdb_1decimal_1width
Java_org_duckdb_DuckDBBindings_duckdb_1decimal_1scale
Expand All @@ -74,6 +85,8 @@ Java_org_duckdb_DuckDBBindings_duckdb_1vector_1get_1data
Java_org_duckdb_DuckDBBindings_duckdb_1vector_1get_1validity
Java_org_duckdb_DuckDBBindings_duckdb_1vector_1ensure_1validity_1writable
Java_org_duckdb_DuckDBBindings_duckdb_1vector_1assign_1string_1element_1len
Java_org_duckdb_DuckDBBindings_duckdb_1vector_1get_1string
Java_org_duckdb_DuckDBBindings_duckdb_1vector_1get_1string__Ljava_nio_ByteBuffer_2J
Java_org_duckdb_DuckDBBindings_duckdb_1validity_1row_1is_1valid
Java_org_duckdb_DuckDBBindings_duckdb_1validity_1set_1row_1validity
Java_org_duckdb_DuckDBBindings_duckdb_1list_1vector_1get_1child
Expand All @@ -98,6 +111,7 @@ Java_org_duckdb_DuckDBBindings_duckdb_1appender_1column_1count
Java_org_duckdb_DuckDBBindings_duckdb_1appender_1column_1type
Java_org_duckdb_DuckDBBindings_duckdb_1append_1data_1chunk
Java_org_duckdb_DuckDBBindings_duckdb_1append_1default_1to_1chunk
Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1set_1function

duckdb_adbc_init
duckdb_add_aggregate_function_to_set
Expand Down Expand Up @@ -306,7 +320,6 @@ duckdb_destroy_selection_vector
duckdb_destroy_table_function
duckdb_destroy_task_state
duckdb_destroy_value
duckdb_destroy_vector
duckdb_disconnect
duckdb_double_to_decimal
duckdb_double_to_hugeint
Expand Down
14 changes: 14 additions & 0 deletions duckdb_java.exp
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,21 @@ _Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1set_1schema
_Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1startup

_Java_org_duckdb_DuckDBBindings_duckdb_1vector_1size
_Java_org_duckdb_DuckDBBindings_duckdb_1create_1scalar_1function
_Java_org_duckdb_DuckDBBindings_duckdb_1destroy_1scalar_1function
_Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1set_1name
_Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1add_1parameter
_Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1set_1return_1type
_Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1set_1varargs
_Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1set_1volatile
_Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1set_1special_1handling
_Java_org_duckdb_DuckDBBindings_duckdb_1register_1scalar_1function
_Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1set_1error
_Java_org_duckdb_DuckDBBindings_duckdb_1vector_1get_1string
_Java_org_duckdb_DuckDBBindings_duckdb_1vector_1get_1string__Ljava_nio_ByteBuffer_2J
_Java_org_duckdb_DuckDBBindings_duckdb_1create_1logical_1type
_Java_org_duckdb_DuckDBBindings_duckdb_1create_1decimal_1type
_Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1set_1function
_Java_org_duckdb_DuckDBBindings_duckdb_1get_1type_1id
_Java_org_duckdb_DuckDBBindings_duckdb_1decimal_1width
_Java_org_duckdb_DuckDBBindings_duckdb_1decimal_1scale
Expand Down
14 changes: 14 additions & 0 deletions duckdb_java.map
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,20 @@ DUCKDB_JAVA {
Java_org_duckdb_DuckDBNative_duckdb_1jdbc_1startup;

Java_org_duckdb_DuckDBBindings_duckdb_1vector_1size;
Java_org_duckdb_DuckDBBindings_duckdb_1create_1scalar_1function;
Java_org_duckdb_DuckDBBindings_duckdb_1destroy_1scalar_1function;
Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1set_1name;
Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1add_1parameter;
Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1set_1return_1type;
Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1set_1varargs;
Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1set_1volatile;
Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1set_1special_1handling;
Java_org_duckdb_DuckDBBindings_duckdb_1register_1scalar_1function;
Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1set_1error;
Java_org_duckdb_DuckDBBindings_duckdb_1vector_1get_1string;
Java_org_duckdb_DuckDBBindings_duckdb_1vector_1get_1string__Ljava_nio_ByteBuffer_2J;
Java_org_duckdb_DuckDBBindings_duckdb_1create_1logical_1type;
Java_org_duckdb_DuckDBBindings_duckdb_1create_1decimal_1type;
Java_org_duckdb_DuckDBBindings_duckdb_1get_1type_1id;
Java_org_duckdb_DuckDBBindings_duckdb_1decimal_1width;
Java_org_duckdb_DuckDBBindings_duckdb_1decimal_1scale;
Expand Down Expand Up @@ -97,6 +110,7 @@ DUCKDB_JAVA {
Java_org_duckdb_DuckDBBindings_duckdb_1appender_1column_1type;
Java_org_duckdb_DuckDBBindings_duckdb_1append_1data_1chunk;
Java_org_duckdb_DuckDBBindings_duckdb_1append_1default_1to_1chunk;
Java_org_duckdb_DuckDBBindings_duckdb_1scalar_1function_1set_1function;

duckdb_adbc_init;
duckdb_add_aggregate_function_to_set;
Expand Down
20 changes: 20 additions & 0 deletions src/jni/bindings_logical_type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,26 @@ JNIEXPORT jobject JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1create_1logical
return make_ptr_buf(env, lt);
}

/*
* Class: org_duckdb_DuckDBBindings
* Method: duckdb_create_decimal_type
* Signature: (II)Ljava/nio/ByteBuffer;
*/
JNIEXPORT jobject JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1create_1decimal_1type(JNIEnv *env, jclass, jint width,
jint scale) {

if (width < 1 || width > 38) {
env->ThrowNew(J_SQLException, "Invalid decimal width: expected 1..38");
return nullptr;
}
if (scale < 0 || scale > width) {
env->ThrowNew(J_SQLException, "Invalid decimal scale: expected 0..width");
return nullptr;
}
duckdb_logical_type lt = duckdb_create_decimal_type(static_cast<uint8_t>(width), static_cast<uint8_t>(scale));
return make_ptr_buf(env, lt);
}

/*
* Class: org_duckdb_DuckDBBindings
* Method: duckdb_get_type_id
Expand Down
Loading
Loading