diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e51ce504..52cbead46 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -592,8 +592,12 @@ add_library(duckdb_java SHARED src/jni/bindings_data_chunk.cpp src/jni/bindings_logical_type.cpp src/jni/bindings_scalar_function.cpp + src/jni/bindings_table_function.cpp + src/jni/bindings_table_function_bind.cpp + src/jni/bindings_table_function_init.cpp src/jni/bindings_validity.cpp src/jni/bindings_vector.cpp + src/jni/bindings_value.cpp src/jni/config.cpp src/jni/duckdb_java.cpp src/jni/functions.cpp diff --git a/CMakeLists.txt.in b/CMakeLists.txt.in index 928575a56..4de07bb70 100644 --- a/CMakeLists.txt.in +++ b/CMakeLists.txt.in @@ -110,8 +110,12 @@ add_library(duckdb_java SHARED src/jni/bindings_data_chunk.cpp src/jni/bindings_logical_type.cpp src/jni/bindings_scalar_function.cpp + src/jni/bindings_table_function.cpp + src/jni/bindings_table_function_bind.cpp + src/jni/bindings_table_function_init.cpp src/jni/bindings_validity.cpp src/jni/bindings_vector.cpp + src/jni/bindings_value.cpp src/jni/config.cpp src/jni/duckdb_java.cpp src/jni/functions.cpp diff --git a/UDF.MD b/UDF.MD index 8e4b83dd3..313cee0a6 100644 --- a/UDF.MD +++ b/UDF.MD @@ -87,8 +87,9 @@ Common class mappings include: Notes: -- `UHUGEINT` is supported through explicit `DuckDBColumnType.UHUGEINT`/`DuckDBLogicalType` declarations. -- Java class auto-mapping for `BigInteger` remains `HUGEINT`. +- `UHUGEINT` is supported through explicit `DuckDBColumnType.UHUGEINT`/`DuckDBLogicalType` declarations +- Java class auto-mapping for `BigInteger` remains `HUGEINT` +- composite types like `STRUCT` or `LIST` are currently not supported, this intended to be added in future versions Use `DuckDBLogicalType.decimal(width, scale)` for explicit DECIMAL precision/scale. @@ -153,9 +154,6 @@ This registry provides bookkeeping for functions registered through the JDBC API - `name()` - `functionKind()` -- `isScalar()` -- parameter and return type metadata -- callback and flags used at registration time To inspect Java-side registrations: @@ -207,3 +205,68 @@ try (Connection conn = DriverManager.getConnection("jdbc:duckdb:"); Statement st ```sql SELECT java_event_label(TIMESTAMP '2026-04-04 12:00:00', 'launch', 4.5); ``` + +## Table functions (`DuckDBTableFunction`) + +Similar to scalar functions, a table function can be registered using `DuckDBFunctions.tableFunction()`. + +Table function callback must implement `DuckDBTableFunction` interface: + + - `bind` - called during `PREPARE` stage, must register output columns; has access to input parameters using `.getParameter()` and `.getNamedParameter()`; can return a "bind data" object + that can be used during function execution + - `init` - called once before the function execution, has access to bind data, returns an object used to track the state during function execution + - `localInit` (optional) - called once for every thread before the function execution, returns an object used to track thread-local state during function execution + - `apply` - has access to bind data, global init data and local init date, writes results into output vectors, returns a number of written row, is called by the engine repeatedly until it return zero rows + +All callback arguments (including parameter objects) are only valid during the callback execution. + +In general, Java table functions interface exposes the [C API table function interface](https://github.com/duckdb/duckdb-java/blob/2f4ed44df2fd1f89594cc7af28f9f8daba5fa582/src/duckdb/src/include/duckdb.h#L4145-L4478) +as close as possible. + +Example: + +```java +try (Connection conn = DriverManager.getConnection("jdbc:duckdb:"); Statement stmt = conn.createStatement()) { + DuckDBFunctions.tableFunction() + .withName("java_table_basic") + .withParameter(int.class) + .withNamedParameter("param1", String.class) + .withFunction(new DuckDBTableFunction() { + @Override + public Integer bind(DuckDBTableFunctionBindInfo info) throws Exception { + info.addResultColumn("col1", Integer.TYPE).addResultColumn("col2", String.class); + DuckDBValue param = info.getParameter(0); + assertThrows(param::getBoolean, FunctionException.class); + DuckDBValue namedParam = info.getNamedParameter("param1"); + assertEquals(namedParam.getString(), "foobar"); + return param.getInt(); + } + + @Override + public AtomicBoolean init(DuckDBTableFunctionInitInfo info) throws Exception { + return new AtomicBoolean(false); + } + + @Override + public long apply(DuckDBTableFunctionCallInfo info, DuckDBDataChunkWriter output) throws Exception { + Integer bindData = info.getBindData(); + AtomicBoolean done = info.getInitData(); + if (done.get()) { + return 0; + } + output.vector(0).setInt(0, bindData); + output.vector(1).setString(0, "foo"); + output.vector(0).setNull(1); + output.vector(1).setString(1, "bar"); + done.set(true); + return 2; + } + }) + .register(conn); +... +} +``` +```sql +FROM java_table_basic(42, param1='foobar') +``` + diff --git a/duckdb_java.def b/duckdb_java.def index 503344a45..7ae4d081b 100644 --- a/duckdb_java.def +++ b/duckdb_java.def @@ -114,6 +114,67 @@ 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_1bind_1add_1result_1column +Java_org_duckdb_DuckDBBindings_duckdb_1bind_1get_1parameter_1count +Java_org_duckdb_DuckDBBindings_duckdb_1bind_1get_1parameter +Java_org_duckdb_DuckDBBindings_duckdb_1bind_1get_1named_1parameter +Java_org_duckdb_DuckDBBindings_duckdb_1bind_1set_1bind_1data +Java_org_duckdb_DuckDBBindings_duckdb_1bind_1set_1cardinality +Java_org_duckdb_DuckDBBindings_duckdb_1bind_1set_1error + +Java_org_duckdb_DuckDBBindings_duckdb_1init_1get_1bind_1data +Java_org_duckdb_DuckDBBindings_duckdb_1init_1set_1init_1data +Java_org_duckdb_DuckDBBindings_duckdb_1init_1get_1column_1count +Java_org_duckdb_DuckDBBindings_duckdb_1init_1get_1column_1index +Java_org_duckdb_DuckDBBindings_duckdb_1init_1set_1max_1threads +Java_org_duckdb_DuckDBBindings_duckdb_1init_1set_1error + +Java_org_duckdb_DuckDBBindings_duckdb_1create_1table_1function +Java_org_duckdb_DuckDBBindings_duckdb_1destroy_1table_1function +Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1name +Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1add_1parameter +Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1add_1named_1parameter +Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1extra_1info +Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1bind +Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1init +Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1local_1init +Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1function +Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1supports_1projection_1pushdown +Java_org_duckdb_DuckDBBindings_duckdb_1register_1table_1function +Java_org_duckdb_DuckDBBindings_duckdb_1function_1get_1bind_1data +Java_org_duckdb_DuckDBBindings_duckdb_1function_1get_1init_1data +Java_org_duckdb_DuckDBBindings_duckdb_1function_1get_1local_1init_1data +Java_org_duckdb_DuckDBBindings_duckdb_1function_1set_1error + +Java_org_duckdb_DuckDBBindings_duckdb_1is_1null_1value +Java_org_duckdb_DuckDBBindings_duckdb_1get_1value_1type +Java_org_duckdb_DuckDBBindings_duckdb_1destroy_1value +Java_org_duckdb_DuckDBBindings_duckdb_1get_1bool +Java_org_duckdb_DuckDBBindings_duckdb_1get_1int8 +Java_org_duckdb_DuckDBBindings_duckdb_1get_1uint8 +Java_org_duckdb_DuckDBBindings_duckdb_1get_1int16 +Java_org_duckdb_DuckDBBindings_duckdb_1get_1uint16 +Java_org_duckdb_DuckDBBindings_duckdb_1get_1int32 +Java_org_duckdb_DuckDBBindings_duckdb_1get_1uint32 +Java_org_duckdb_DuckDBBindings_duckdb_1get_1int64 +Java_org_duckdb_DuckDBBindings_duckdb_1get_1uint64 +Java_org_duckdb_DuckDBBindings_duckdb_1get_1hugeint +Java_org_duckdb_DuckDBBindings_duckdb_1get_1uhugeint +Java_org_duckdb_DuckDBBindings_duckdb_1get_1bignum +Java_org_duckdb_DuckDBBindings_duckdb_1get_1decimal +Java_org_duckdb_DuckDBBindings_duckdb_1get_1float +Java_org_duckdb_DuckDBBindings_duckdb_1get_1double +Java_org_duckdb_DuckDBBindings_duckdb_1get_1date +Java_org_duckdb_DuckDBBindings_duckdb_1get_1time +Java_org_duckdb_DuckDBBindings_duckdb_1get_1time_1ns +Java_org_duckdb_DuckDBBindings_duckdb_1get_1time_1tz +Java_org_duckdb_DuckDBBindings_duckdb_1get_1timestamp +Java_org_duckdb_DuckDBBindings_duckdb_1get_1timestamp_1tz +Java_org_duckdb_DuckDBBindings_duckdb_1get_1timestamp_1s +Java_org_duckdb_DuckDBBindings_duckdb_1get_1timestamp_1ms +Java_org_duckdb_DuckDBBindings_duckdb_1get_1timestamp_1ns +Java_org_duckdb_DuckDBBindings_duckdb_1get_1varchar + duckdb_adbc_init duckdb_add_aggregate_function_to_set duckdb_add_replacement_scan diff --git a/duckdb_java.exp b/duckdb_java.exp index 5d2f47f4f..1e92c19c7 100644 --- a/duckdb_java.exp +++ b/duckdb_java.exp @@ -111,6 +111,67 @@ _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_1bind_1add_1result_1column +_Java_org_duckdb_DuckDBBindings_duckdb_1bind_1get_1parameter_1count +_Java_org_duckdb_DuckDBBindings_duckdb_1bind_1get_1parameter +_Java_org_duckdb_DuckDBBindings_duckdb_1bind_1get_1named_1parameter +_Java_org_duckdb_DuckDBBindings_duckdb_1bind_1set_1bind_1data +_Java_org_duckdb_DuckDBBindings_duckdb_1bind_1set_1cardinality +_Java_org_duckdb_DuckDBBindings_duckdb_1bind_1set_1error + +_Java_org_duckdb_DuckDBBindings_duckdb_1init_1get_1bind_1data +_Java_org_duckdb_DuckDBBindings_duckdb_1init_1set_1init_1data +_Java_org_duckdb_DuckDBBindings_duckdb_1init_1get_1column_1count +_Java_org_duckdb_DuckDBBindings_duckdb_1init_1get_1column_1index +_Java_org_duckdb_DuckDBBindings_duckdb_1init_1set_1max_1threads +_Java_org_duckdb_DuckDBBindings_duckdb_1init_1set_1error + +_Java_org_duckdb_DuckDBBindings_duckdb_1create_1table_1function +_Java_org_duckdb_DuckDBBindings_duckdb_1destroy_1table_1function +_Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1name +_Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1add_1parameter +_Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1add_1named_1parameter +_Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1extra_1info +_Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1bind +_Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1init +_Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1local_1init +_Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1function +_Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1supports_1projection_1pushdown +_Java_org_duckdb_DuckDBBindings_duckdb_1register_1table_1function +_Java_org_duckdb_DuckDBBindings_duckdb_1function_1get_1bind_1data +_Java_org_duckdb_DuckDBBindings_duckdb_1function_1get_1init_1data +_Java_org_duckdb_DuckDBBindings_duckdb_1function_1get_1local_1init_1data +_Java_org_duckdb_DuckDBBindings_duckdb_1function_1set_1error + +_Java_org_duckdb_DuckDBBindings_duckdb_1is_1null_1value +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1value_1type +_Java_org_duckdb_DuckDBBindings_duckdb_1destroy_1value +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1bool +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1int8 +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1uint8 +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1int16 +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1uint16 +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1int32 +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1uint32 +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1int64 +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1uint64 +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1hugeint +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1uhugeint +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1bignum +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1decimal +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1float +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1double +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1date +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1time +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1time_1ns +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1time_1tz +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1timestamp +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1timestamp_1tz +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1timestamp_1s +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1timestamp_1ms +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1timestamp_1ns +_Java_org_duckdb_DuckDBBindings_duckdb_1get_1varchar + _duckdb_adbc_init _duckdb_add_aggregate_function_to_set _duckdb_add_replacement_scan diff --git a/duckdb_java.map b/duckdb_java.map index 435064f74..f3b476f8c 100644 --- a/duckdb_java.map +++ b/duckdb_java.map @@ -113,6 +113,67 @@ DUCKDB_JAVA { Java_org_duckdb_DuckDBBindings_duckdb_1append_1data_1chunk; Java_org_duckdb_DuckDBBindings_duckdb_1append_1default_1to_1chunk; + Java_org_duckdb_DuckDBBindings_duckdb_1bind_1add_1result_1column; + Java_org_duckdb_DuckDBBindings_duckdb_1bind_1get_1parameter_1count; + Java_org_duckdb_DuckDBBindings_duckdb_1bind_1get_1parameter; + Java_org_duckdb_DuckDBBindings_duckdb_1bind_1get_1named_1parameter; + Java_org_duckdb_DuckDBBindings_duckdb_1bind_1set_1bind_1data; + Java_org_duckdb_DuckDBBindings_duckdb_1bind_1set_1cardinality; + Java_org_duckdb_DuckDBBindings_duckdb_1bind_1set_1error; + + Java_org_duckdb_DuckDBBindings_duckdb_1init_1get_1bind_1data; + Java_org_duckdb_DuckDBBindings_duckdb_1init_1set_1init_1data; + Java_org_duckdb_DuckDBBindings_duckdb_1init_1get_1column_1count; + Java_org_duckdb_DuckDBBindings_duckdb_1init_1get_1column_1index; + Java_org_duckdb_DuckDBBindings_duckdb_1init_1set_1max_1threads; + Java_org_duckdb_DuckDBBindings_duckdb_1init_1set_1error; + + Java_org_duckdb_DuckDBBindings_duckdb_1create_1table_1function; + Java_org_duckdb_DuckDBBindings_duckdb_1destroy_1table_1function; + Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1name; + Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1add_1parameter; + Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1add_1named_1parameter; + Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1extra_1info; + Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1bind; + Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1init; + Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1local_1init; + Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1function; + Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1supports_1projection_1pushdown; + Java_org_duckdb_DuckDBBindings_duckdb_1register_1table_1function; + Java_org_duckdb_DuckDBBindings_duckdb_1function_1get_1bind_1data; + Java_org_duckdb_DuckDBBindings_duckdb_1function_1get_1init_1data; + Java_org_duckdb_DuckDBBindings_duckdb_1function_1get_1local_1init_1data; + Java_org_duckdb_DuckDBBindings_duckdb_1function_1set_1error; + + Java_org_duckdb_DuckDBBindings_duckdb_1is_1null_1value; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1value_1type; + Java_org_duckdb_DuckDBBindings_duckdb_1destroy_1value; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1bool; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1int8; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1uint8; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1int16; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1uint16; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1int32; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1uint32; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1int64; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1uint64; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1hugeint; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1uhugeint; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1bignum; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1decimal; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1float; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1double; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1date; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1time; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1time_1ns; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1time_1tz; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1timestamp; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1timestamp_1tz; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1timestamp_1s; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1timestamp_1ms; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1timestamp_1ns; + Java_org_duckdb_DuckDBBindings_duckdb_1get_1varchar; + duckdb_adbc_init; duckdb_add_aggregate_function_to_set; duckdb_add_replacement_scan; diff --git a/src/jni/bindings_table_function.cpp b/src/jni/bindings_table_function.cpp new file mode 100644 index 000000000..bb93484aa --- /dev/null +++ b/src/jni/bindings_table_function.cpp @@ -0,0 +1,452 @@ + +#include "bindings.hpp" +#include "holders.hpp" +#include "refs.hpp" +#include "util.hpp" + +static duckdb_table_function table_function_buf_to_table_function(JNIEnv *env, jobject table_function_buf) { + if (table_function_buf == nullptr) { + env->ThrowNew(J_SQLException, "Invalid table function buffer"); + return nullptr; + } + + auto table_function = reinterpret_cast(env->GetDirectBufferAddress(table_function_buf)); + if (table_function == nullptr) { + env->ThrowNew(J_SQLException, "Invalid table function"); + return nullptr; + } + return table_function; +} + +static duckdb_function_info function_info_buf_to_function_info(JNIEnv *env, jobject function_info_buf) { + if (function_info_buf == nullptr) { + env->ThrowNew(J_SQLException, "Invalid function info buffer"); + return nullptr; + } + + auto function_info = reinterpret_cast(env->GetDirectBufferAddress(function_info_buf)); + if (function_info == nullptr) { + env->ThrowNew(J_SQLException, "Invalid function info"); + return nullptr; + } + return function_info; +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_create_table_function + * Signature: ()Ljava/nio/ByteBuffer; + */ +JNIEXPORT jobject JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1create_1table_1function(JNIEnv *env, jclass) { + + duckdb_table_function tf = duckdb_create_table_function(); + + return make_ptr_buf(env, tf); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_destroy_table_function + * Signature: (Ljava/nio/ByteBuffer;)V + */ +JNIEXPORT void JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1destroy_1table_1function(JNIEnv *env, jclass, + jobject table_function) { + + duckdb_table_function tf = table_function_buf_to_table_function(env, table_function); + if (env->ExceptionCheck()) { + return; + } + + duckdb_destroy_table_function(&tf); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_table_function_set_name + * Signature: (Ljava/nio/ByteBuffer;[B)V + */ +JNIEXPORT void JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1name(JNIEnv *env, jclass, + jobject table_function, + jbyteArray name) { + + duckdb_table_function tf = table_function_buf_to_table_function(env, table_function); + if (env->ExceptionCheck()) { + return; + } + std::string name_str = jbyteArray_to_string(env, name); + if (env->ExceptionCheck()) { + return; + } + if (name_str.empty()) { + env->ThrowNew(J_SQLException, "Specified function name must be not empty"); + return; + } + + duckdb_table_function_set_name(tf, name_str.c_str()); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_table_function_add_parameter + * Signature: (Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;)V + */ +JNIEXPORT void JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1add_1parameter(JNIEnv *env, jclass, + jobject table_function, + jobject logical_type) { + + duckdb_table_function tf = table_function_buf_to_table_function(env, table_function); + if (env->ExceptionCheck()) { + return; + } + duckdb_logical_type lt = logical_type_buf_to_logical_type(env, logical_type); + if (env->ExceptionCheck()) { + return; + } + + duckdb_table_function_add_parameter(tf, lt); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_table_function_add_named_parameter + * Signature: (Ljava/nio/ByteBuffer;[BLjava/nio/ByteBuffer;)V + */ +JNIEXPORT void JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1add_1named_1parameter( + JNIEnv *env, jclass, jobject table_function, jbyteArray name, jobject logical_type) { + + duckdb_table_function tf = table_function_buf_to_table_function(env, table_function); + if (env->ExceptionCheck()) { + return; + } + std::string name_str = jbyteArray_to_string(env, name); + if (env->ExceptionCheck()) { + return; + } + if (name_str.empty()) { + env->ThrowNew(J_SQLException, "Specified parameter name must be not empty"); + return; + } + duckdb_logical_type lt = logical_type_buf_to_logical_type(env, logical_type); + if (env->ExceptionCheck()) { + return; + } + + duckdb_table_function_add_named_parameter(tf, name_str.c_str(), lt); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_table_function_set_extra_info + * Signature: (Ljava/nio/ByteBuffer;Lorg/duckdb/DuckDBTableFunctionWrapper;)V + */ +JNIEXPORT void JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1extra_1info(JNIEnv *env, jclass, + jobject table_function, + jobject callback) { + + if (callback == nullptr) { + env->ThrowNew(J_SQLException, "Specified callback must be not null"); + return; + } + + duckdb_table_function tf = table_function_buf_to_table_function(env, table_function); + if (env->ExceptionCheck()) { + return; + } + + auto callback_holder = std::unique_ptr(new GlobalRefHolder(env, callback)); + if (callback_holder->vm == nullptr) { + env->ThrowNew(J_SQLException, "Unable to create a global reference to the specified table function callback"); + return; + } + + duckdb_table_function_set_extra_info(tf, callback_holder.release(), GlobalRefHolder::destroy); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_table_function_set_bind + * Signature: (Ljava/nio/ByteBuffer;)V + */ +JNIEXPORT void JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1bind(JNIEnv *env, jclass, + jobject table_function) { + + duckdb_table_function tf = table_function_buf_to_table_function(env, table_function); + if (env->ExceptionCheck()) { + return; + } + + duckdb_table_function_set_bind(tf, [](duckdb_bind_info info) { + auto callback_holder = reinterpret_cast(duckdb_bind_get_extra_info(info)); + if (callback_holder == nullptr) { + duckdb_bind_set_error(info, "Table function callback not found"); + return; + } + AttachedJNIEnv attached = callback_holder->attach_current_thread(); + if (attached.env == nullptr) { + duckdb_bind_set_error(info, "Unable to attach JNI environment"); + return; + } + jobject info_buf = make_ptr_buf(attached.env, info); + if (info_buf == nullptr) { + duckdb_bind_set_error(info, "Unable to create bind info buffer"); + return; + } + LocalRefHolder info_buf_holder(attached.env, info_buf); + + attached.env->CallVoidMethod(callback_holder->global_ref, J_DuckDBTableFunctionWrapper_executeBind, info_buf); + if (attached.env->ExceptionCheck()) { + duckdb_bind_set_error(info, "Java callback system error"); + } + }); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_table_function_set_init + * Signature: (Ljava/nio/ByteBuffer;)V + */ +JNIEXPORT void JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1init(JNIEnv *env, jclass, + jobject table_function) { + + duckdb_table_function tf = table_function_buf_to_table_function(env, table_function); + if (env->ExceptionCheck()) { + return; + } + + duckdb_table_function_set_init(tf, [](duckdb_init_info info) { + auto callback_holder = reinterpret_cast(duckdb_init_get_extra_info(info)); + if (callback_holder == nullptr) { + duckdb_init_set_error(info, "Table function callback not found"); + return; + } + AttachedJNIEnv attached = callback_holder->attach_current_thread(); + if (attached.env == nullptr) { + duckdb_init_set_error(info, "Unable to attach JNI environment"); + return; + } + jobject info_buf = make_ptr_buf(attached.env, info); + if (info_buf == nullptr) { + duckdb_init_set_error(info, "Unable to create global init info buffer"); + return; + } + LocalRefHolder info_buf_holder(attached.env, info_buf); + + attached.env->CallVoidMethod(callback_holder->global_ref, J_DuckDBTableFunctionWrapper_executeGlobalInit, + info_buf); + if (attached.env->ExceptionCheck()) { + duckdb_init_set_error(info, "Java callback system error"); + } + }); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_table_function_set_local_init + * Signature: (Ljava/nio/ByteBuffer;)V + */ +JNIEXPORT void JNICALL +Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1local_1init(JNIEnv *env, jclass, jobject table_function) { + + duckdb_table_function tf = table_function_buf_to_table_function(env, table_function); + if (env->ExceptionCheck()) { + return; + } + + duckdb_table_function_set_local_init(tf, [](duckdb_init_info info) { + auto callback_holder = reinterpret_cast(duckdb_init_get_extra_info(info)); + if (callback_holder == nullptr) { + duckdb_init_set_error(info, "Table function callback not found"); + return; + } + AttachedJNIEnv attached = callback_holder->attach_current_thread(); + if (attached.env == nullptr) { + duckdb_init_set_error(info, "Unable to attach JNI environment"); + return; + } + jobject info_buf = make_ptr_buf(attached.env, info); + if (info_buf == nullptr) { + duckdb_init_set_error(info, "Unable to create local init info buffer"); + return; + } + LocalRefHolder info_buf_holder(attached.env, info_buf); + + attached.env->CallVoidMethod(callback_holder->global_ref, J_DuckDBTableFunctionWrapper_executeLocalInit, + info_buf); + if (attached.env->ExceptionCheck()) { + duckdb_init_set_error(info, "Java callback system error"); + } + }); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_table_function_set_function + * Signature: (Ljava/nio/ByteBuffer;)V + */ +JNIEXPORT void JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1set_1function(JNIEnv *env, jclass, + jobject table_function) { + + duckdb_table_function tf = table_function_buf_to_table_function(env, table_function); + if (env->ExceptionCheck()) { + return; + } + + duckdb_table_function_set_function(tf, [](duckdb_function_info info, duckdb_data_chunk output) { + auto callback_holder = reinterpret_cast(duckdb_function_get_extra_info(info)); + if (callback_holder == nullptr) { + duckdb_function_set_error(info, "Table function callback not found"); + return; + } + AttachedJNIEnv attached = callback_holder->attach_current_thread(); + if (attached.env == nullptr) { + duckdb_function_set_error(info, "Unable to attach JNI environment"); + return; + } + jobject info_buf = make_ptr_buf(attached.env, info); + if (info_buf == nullptr) { + duckdb_function_set_error(info, "Unable to create function info buffer"); + return; + } + LocalRefHolder info_buf_holder(attached.env, info_buf); + jobject output_buf = make_ptr_buf(attached.env, output); + if (output_buf == nullptr) { + duckdb_function_set_error(info, "Unable to create output buffer"); + return; + } + LocalRefHolder output_buf_holder(attached.env, output_buf); + + attached.env->CallVoidMethod(callback_holder->global_ref, J_DuckDBTableFunctionWrapper_executeFunction, + info_buf, output_buf); + if (attached.env->ExceptionCheck()) { + duckdb_function_set_error(info, "Java callback system error"); + } + }); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_table_function_supports_projection_pushdown + * Signature: (Ljava/nio/ByteBuffer;Z)V + */ +JNIEXPORT void JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1table_1function_1supports_1projection_1pushdown( + JNIEnv *env, jclass, jobject table_function, jboolean pushdown) { + + duckdb_table_function tf = table_function_buf_to_table_function(env, table_function); + if (env->ExceptionCheck()) { + return; + } + + bool pushdown_flag = !!pushdown; + duckdb_table_function_supports_projection_pushdown(tf, pushdown_flag); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_register_table_function + * Signature: (Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;)I + */ +JNIEXPORT jint JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1register_1table_1function(JNIEnv *env, jclass, + jobject connection, + jobject table_function) { + + auto conn = conn_ref_buf_to_conn(env, connection); + if (env->ExceptionCheck()) { + return static_cast(DuckDBError); + } + duckdb_table_function tf = table_function_buf_to_table_function(env, table_function); + if (env->ExceptionCheck()) { + return static_cast(DuckDBError); + } + + duckdb_state state = duckdb_register_table_function(conn, tf); + + return static_cast(state); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_function_get_bind_data + * Signature: (Ljava/nio/ByteBuffer;)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1function_1get_1bind_1data(JNIEnv *env, jclass, + jobject function_info) { + + duckdb_function_info fi = function_info_buf_to_function_info(env, function_info); + if (env->ExceptionCheck()) { + return nullptr; + } + + void *bind_data = duckdb_function_get_bind_data(fi); + if (bind_data == nullptr) { + return nullptr; + } + + GlobalRefHolder *holder = reinterpret_cast(bind_data); + return holder->global_ref; +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_function_get_init_data + * Signature: (Ljava/nio/ByteBuffer;)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1function_1get_1init_1data(JNIEnv *env, jclass, + jobject function_info) { + + duckdb_function_info fi = function_info_buf_to_function_info(env, function_info); + if (env->ExceptionCheck()) { + return nullptr; + } + + void *global_init_data = duckdb_function_get_init_data(fi); + if (global_init_data == nullptr) { + return nullptr; + } + + GlobalRefHolder *holder = reinterpret_cast(global_init_data); + return holder->global_ref; +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_function_get_local_init_data + * Signature: (Ljava/nio/ByteBuffer;)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL +Java_org_duckdb_DuckDBBindings_duckdb_1function_1get_1local_1init_1data(JNIEnv *env, jclass, jobject function_info) { + + duckdb_function_info fi = function_info_buf_to_function_info(env, function_info); + if (env->ExceptionCheck()) { + return nullptr; + } + + void *local_init_data = duckdb_function_get_local_init_data(fi); + if (local_init_data == nullptr) { + return nullptr; + } + + GlobalRefHolder *holder = reinterpret_cast(local_init_data); + return holder->global_ref; +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_function_set_error + * Signature: (Ljava/nio/ByteBuffer;[B)V + */ +JNIEXPORT void JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1function_1set_1error(JNIEnv *env, jclass, + jobject function_info, + jbyteArray error) { + + duckdb_function_info fi = function_info_buf_to_function_info(env, function_info); + if (env->ExceptionCheck()) { + return; + } + + std::string error_str = jbyteArray_to_string(env, error); + if (env->ExceptionCheck()) { + return; + } + + duckdb_function_set_error(fi, error_str.c_str()); +} diff --git a/src/jni/bindings_table_function_bind.cpp b/src/jni/bindings_table_function_bind.cpp new file mode 100644 index 000000000..80c63cd9e --- /dev/null +++ b/src/jni/bindings_table_function_bind.cpp @@ -0,0 +1,177 @@ +#include "bindings.hpp" +#include "holders.hpp" +#include "refs.hpp" +#include "util.hpp" + +static duckdb_bind_info bind_info_buf_to_bind_info(JNIEnv *env, jobject bind_info_buf) { + if (bind_info_buf == nullptr) { + env->ThrowNew(J_SQLException, "Invalid table function bind info buffer"); + return nullptr; + } + auto bind_info = reinterpret_cast(env->GetDirectBufferAddress(bind_info_buf)); + if (bind_info == nullptr) { + env->ThrowNew(J_SQLException, "Invalid table function bind info"); + return nullptr; + } + return bind_info; +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_bind_add_result_column + * Signature: (Ljava/nio/ByteBuffer;[BLjava/nio/ByteBuffer;)V + */ +JNIEXPORT void JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1bind_1add_1result_1column(JNIEnv *env, jclass, + jobject bind_info, + jbyteArray name, + jobject logical_type) { + + duckdb_bind_info bi = bind_info_buf_to_bind_info(env, bind_info); + if (env->ExceptionCheck()) { + return; + } + std::string name_str = jbyteArray_to_string(env, name); + if (env->ExceptionCheck()) { + return; + } + if (name_str.empty()) { + env->ThrowNew(J_SQLException, "Specified result column name must be not empty"); + return; + } + duckdb_logical_type lt = logical_type_buf_to_logical_type(env, logical_type); + if (env->ExceptionCheck()) { + return; + } + + duckdb_bind_add_result_column(bi, name_str.c_str(), lt); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_bind_get_parameter_count + * Signature: (Ljava/nio/ByteBuffer;)J + */ +JNIEXPORT jlong JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1bind_1get_1parameter_1count(JNIEnv *env, jclass, + jobject bind_info) { + + duckdb_bind_info bi = bind_info_buf_to_bind_info(env, bind_info); + if (env->ExceptionCheck()) { + return 0; + } + + idx_t count = duckdb_bind_get_parameter_count(bi); + + return static_cast(count); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_bind_get_parameter + * Signature: (Ljava/nio/ByteBuffer;J)Ljava/nio/ByteBuffer; + */ +JNIEXPORT jobject JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1bind_1get_1parameter(JNIEnv *env, jclass, + jobject bind_info, jlong index) { + + duckdb_bind_info bi = bind_info_buf_to_bind_info(env, bind_info); + if (env->ExceptionCheck()) { + return nullptr; + } + idx_t index_idx = jlong_to_idx(env, index); + if (env->ExceptionCheck()) { + return nullptr; + } + + duckdb_value val = duckdb_bind_get_parameter(bi, index_idx); + + return make_ptr_buf(env, val); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_bind_get_named_parameter + * Signature: (Ljava/nio/ByteBuffer;[B)Ljava/nio/ByteBuffer; + */ +JNIEXPORT jobject JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1bind_1get_1named_1parameter(JNIEnv *env, jclass, + jobject bind_info, + jbyteArray name) { + + duckdb_bind_info bi = bind_info_buf_to_bind_info(env, bind_info); + if (env->ExceptionCheck()) { + return nullptr; + } + std::string name_str = jbyteArray_to_string(env, name); + if (env->ExceptionCheck()) { + return nullptr; + } + + duckdb_value val = duckdb_bind_get_named_parameter(bi, name_str.c_str()); + + return make_ptr_buf(env, val); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_bind_set_bind_data + * Signature: (Ljava/nio/ByteBuffer;Ljava/lang/Object;)V + */ +JNIEXPORT void JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1bind_1set_1bind_1data(JNIEnv *env, jclass, + jobject bind_info, + jobject bind_data) { + + if (bind_data == nullptr) { + return; + } + + duckdb_bind_info bi = bind_info_buf_to_bind_info(env, bind_info); + if (env->ExceptionCheck()) { + return; + } + auto bind_data_holder = std::unique_ptr(new GlobalRefHolder(env, bind_data)); + if (bind_data_holder->vm == nullptr) { + env->ThrowNew(J_SQLException, "Unable to create a global reference to the specified bind data"); + return; + } + + duckdb_bind_set_bind_data(bi, bind_data_holder.release(), GlobalRefHolder::destroy); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_bind_set_cardinality + * Signature: (Ljava/nio/ByteBuffer;JZ)V + */ +JNIEXPORT void JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1bind_1set_1cardinality(JNIEnv *env, jclass, + jobject bind_info, + jlong cardinality, + jboolean is_exact) { + duckdb_bind_info bi = bind_info_buf_to_bind_info(env, bind_info); + if (env->ExceptionCheck()) { + return; + } + idx_t cardinality_idx = jlong_to_idx(env, cardinality); + if (env->ExceptionCheck()) { + return; + } + bool exact = !!is_exact; + + duckdb_bind_set_cardinality(bi, cardinality_idx, exact); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_bind_set_error + * Signature: (Ljava/nio/ByteBuffer;[B)V + */ +JNIEXPORT void JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1bind_1set_1error(JNIEnv *env, jclass, jobject bind_info, + jbyteArray error) { + duckdb_bind_info bi = bind_info_buf_to_bind_info(env, bind_info); + if (env->ExceptionCheck()) { + return; + } + std::string error_str = jbyteArray_to_string(env, error); + if (env->ExceptionCheck()) { + return; + } + + duckdb_bind_set_error(bi, error_str.c_str()); +} diff --git a/src/jni/bindings_table_function_init.cpp b/src/jni/bindings_table_function_init.cpp new file mode 100644 index 000000000..160a712c4 --- /dev/null +++ b/src/jni/bindings_table_function_init.cpp @@ -0,0 +1,144 @@ +#include "bindings.hpp" +#include "holders.hpp" +#include "refs.hpp" +#include "util.hpp" + +static duckdb_init_info init_info_buf_to_init_info(JNIEnv *env, jobject init_info_buf) { + if (init_info_buf == nullptr) { + env->ThrowNew(J_SQLException, "Invalid table function init info buffer"); + return nullptr; + } + auto init_info = reinterpret_cast(env->GetDirectBufferAddress(init_info_buf)); + if (init_info == nullptr) { + env->ThrowNew(J_SQLException, "Invalid table function init info"); + return nullptr; + } + return init_info; +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_init_get_bind_data + * Signature: (Ljava/nio/ByteBuffer;)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1init_1get_1bind_1data(JNIEnv *env, jclass, + jobject init_info) { + + duckdb_init_info ii = init_info_buf_to_init_info(env, init_info); + if (env->ExceptionCheck()) { + return nullptr; + } + + void *bind_data = duckdb_init_get_bind_data(ii); + if (bind_data == nullptr) { + return nullptr; + } + + GlobalRefHolder *holder = reinterpret_cast(bind_data); + return holder->global_ref; +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_init_set_init_data + * Signature: (Ljava/nio/ByteBuffer;Ljava/lang/Object;)V + */ +JNIEXPORT void JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1init_1set_1init_1data(JNIEnv *env, jclass, + jobject init_info, + jobject init_data) { + if (init_data == nullptr) { + return; + } + + duckdb_init_info ii = init_info_buf_to_init_info(env, init_info); + if (env->ExceptionCheck()) { + return; + } + auto init_data_holder = std::unique_ptr(new GlobalRefHolder(env, init_data)); + if (init_data_holder->vm == nullptr) { + env->ThrowNew(J_SQLException, "Unable to create a global reference to the specified init data"); + return; + } + + duckdb_init_set_init_data(ii, init_data_holder.release(), GlobalRefHolder::destroy); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_init_get_column_count + * Signature: (Ljava/nio/ByteBuffer;)J + */ +JNIEXPORT jlong JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1init_1get_1column_1count(JNIEnv *env, jclass, + jobject init_info) { + + duckdb_init_info ii = init_info_buf_to_init_info(env, init_info); + if (env->ExceptionCheck()) { + return 0; + } + + idx_t count = duckdb_init_get_column_count(ii); + return uint64_to_jlong(count); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_init_get_column_index + * Signature: (Ljava/nio/ByteBuffer;J)J + */ +JNIEXPORT jlong JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1init_1get_1column_1index(JNIEnv *env, jclass, + jobject init_info, + jlong column_index) { + + duckdb_init_info ii = init_info_buf_to_init_info(env, init_info); + if (env->ExceptionCheck()) { + return 0; + } + + idx_t column_index_idx = jlong_to_idx(env, column_index); + if (env->ExceptionCheck()) { + return 0; + } + + idx_t idx = duckdb_init_get_column_index(ii, column_index_idx); + return uint64_to_jlong(idx); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_init_set_max_threads + * Signature: (Ljava/nio/ByteBuffer;J)V + */ +JNIEXPORT void JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1init_1set_1max_1threads(JNIEnv *env, jclass, + jobject init_info, + jlong max_threads) { + + duckdb_init_info ii = init_info_buf_to_init_info(env, init_info); + if (env->ExceptionCheck()) { + return; + } + idx_t mt = jlong_to_idx(env, max_threads); + if (env->ExceptionCheck()) { + return; + } + + duckdb_init_set_max_threads(ii, mt); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_init_set_error + * Signature: (Ljava/nio/ByteBuffer;[B)V + */ +JNIEXPORT void JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1init_1set_1error(JNIEnv *env, jclass, jobject init_info, + jbyteArray error) { + duckdb_init_info ii = init_info_buf_to_init_info(env, init_info); + if (env->ExceptionCheck()) { + return; + } + std::string error_str = jbyteArray_to_string(env, error); + if (env->ExceptionCheck()) { + return; + } + + duckdb_init_set_error(ii, error_str.c_str()); +} diff --git a/src/jni/bindings_value.cpp b/src/jni/bindings_value.cpp new file mode 100644 index 000000000..d6495f15c --- /dev/null +++ b/src/jni/bindings_value.cpp @@ -0,0 +1,436 @@ +#include "bindings.hpp" +#include "refs.hpp" +#include "util.hpp" + +#include +#include + +static duckdb_value value_buf_to_value(JNIEnv *env, jobject value_buf) { + if (value_buf == nullptr) { + env->ThrowNew(J_SQLException, "Invalid value buffer"); + return nullptr; + } + + auto value = reinterpret_cast(env->GetDirectBufferAddress(value_buf)); + if (value == nullptr) { + env->ThrowNew(J_SQLException, "Invalid value"); + return nullptr; + } + return value; +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_is_null_value + * Signature: (Ljava/nio/ByteBuffer;)Z + */ +JNIEXPORT jboolean JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1is_1null_1value(JNIEnv *env, jclass, jobject value) { + duckdb_value val = value_buf_to_value(env, value); + if (env->ExceptionCheck()) { + return false; + } + + bool flag = duckdb_is_null_value(val); + return static_cast(flag); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_value_type + * Signature: (Ljava/nio/ByteBuffer;)I + */ +JNIEXPORT jint JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1value_1type(JNIEnv *env, jclass, jobject value) { + + duckdb_value val = value_buf_to_value(env, value); + if (env->ExceptionCheck()) { + return 0; + } + + duckdb_logical_type lt = duckdb_get_value_type(val); + duckdb_type dt = duckdb_get_type_id(lt); + return static_cast(dt); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_destroy_value + * Signature: (Ljava/nio/ByteBuffer;)V + */ +JNIEXPORT void JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1destroy_1value(JNIEnv *env, jclass, jobject value) { + + duckdb_value val = value_buf_to_value(env, value); + if (env->ExceptionCheck()) { + return; + } + + duckdb_destroy_value(&val); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_bool + * Signature: (Ljava/nio/ByteBuffer;)Z + */ +JNIEXPORT jboolean JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1bool(JNIEnv *env, jclass, jobject value) { + + duckdb_value val = value_buf_to_value(env, value); + if (env->ExceptionCheck()) { + return false; + } + bool flag = duckdb_get_bool(val); + return static_cast(flag); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_int8 + * Signature: (Ljava/nio/ByteBuffer;)B + */ +JNIEXPORT jbyte JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1int8(JNIEnv *env, jclass, jobject value) { + + duckdb_value val = value_buf_to_value(env, value); + if (env->ExceptionCheck()) { + return static_cast(0); + } + int8_t num = duckdb_get_int8(val); + return static_cast(num); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_uint8 + * Signature: (Ljava/nio/ByteBuffer;)S + */ +JNIEXPORT jshort JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1uint8(JNIEnv *env, jclass, jobject value) { + + duckdb_value val = value_buf_to_value(env, value); + if (env->ExceptionCheck()) { + return static_cast(0); + } + uint8_t num = duckdb_get_uint8(val); + return static_cast(num); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_int16 + * Signature: (Ljava/nio/ByteBuffer;)S + */ +JNIEXPORT jshort JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1int16(JNIEnv *env, jclass, jobject value) { + + duckdb_value val = value_buf_to_value(env, value); + if (env->ExceptionCheck()) { + return static_cast(0); + } + int16_t num = duckdb_get_int16(val); + return static_cast(num); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_uint16 + * Signature: (Ljava/nio/ByteBuffer;)I + */ +JNIEXPORT jint JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1uint16(JNIEnv *env, jclass, jobject value) { + + duckdb_value val = value_buf_to_value(env, value); + if (env->ExceptionCheck()) { + return static_cast(0); + } + uint16_t num = duckdb_get_uint16(val); + return static_cast(num); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_int32 + * Signature: (Ljava/nio/ByteBuffer;)I + */ +JNIEXPORT jint JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1int32(JNIEnv *env, jclass, jobject value) { + + duckdb_value val = value_buf_to_value(env, value); + if (env->ExceptionCheck()) { + return static_cast(0); + } + int32_t num = duckdb_get_int32(val); + return static_cast(num); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_uint32 + * Signature: (Ljava/nio/ByteBuffer;)J + */ +JNIEXPORT jlong JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1uint32(JNIEnv *env, jclass, jobject value) { + + duckdb_value val = value_buf_to_value(env, value); + if (env->ExceptionCheck()) { + return static_cast(0); + } + uint32_t num = duckdb_get_uint32(val); + return static_cast(num); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_int64 + * Signature: (Ljava/nio/ByteBuffer;)J + */ +JNIEXPORT jlong JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1int64(JNIEnv *env, jclass, jobject value) { + + duckdb_value val = value_buf_to_value(env, value); + if (env->ExceptionCheck()) { + return static_cast(0); + } + int64_t num = duckdb_get_int64(val); + return static_cast(num); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_uint64 + * Signature: (Ljava/nio/ByteBuffer;)J + */ +JNIEXPORT jlong JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1uint64(JNIEnv *env, jclass, jobject value) { + + duckdb_value val = value_buf_to_value(env, value); + if (env->ExceptionCheck()) { + return static_cast(0); + } + uint64_t num = duckdb_get_uint64(val); + return static_cast(num); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_hugeint + * Signature: (Ljava/nio/ByteBuffer;)Ljava/math/BigInteger; + */ +JNIEXPORT jobject JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1hugeint(JNIEnv *env, jclass, jobject value) { + + duckdb_value val = value_buf_to_value(env, value); + if (env->ExceptionCheck()) { + return nullptr; + } + duckdb_hugeint hi = duckdb_get_hugeint(val); + jlong lower = static_cast(hi.lower); + jlong upper = static_cast(hi.upper); + return env->CallStaticObjectMethod(J_HugeInt, J_HugeInt_toBigInteger, lower, upper); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_uhugeint + * Signature: (Ljava/nio/ByteBuffer;)Ljava/math/BigInteger; + */ +JNIEXPORT jobject JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1uhugeint(JNIEnv *env, jclass, jobject value) { + duckdb_value val = value_buf_to_value(env, value); + if (env->ExceptionCheck()) { + return nullptr; + } + duckdb_uhugeint uhi = duckdb_get_uhugeint(val); + jlong lower = static_cast(uhi.lower); + jlong upper = static_cast(uhi.upper); + return env->CallStaticObjectMethod(J_HugeInt, J_HugeInt_toBigInteger, lower, upper); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_bignum + * Signature: (Ljava/nio/ByteBuffer;)Ljava/math/BigInteger; + */ +JNIEXPORT jobject JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1bignum(JNIEnv *env, jclass, jobject value) { + (void)env; + (void)value; + return nullptr; +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_decimal + * Signature: (Ljava/nio/ByteBuffer;)Ljava/math/BigDecimal; + */ +JNIEXPORT jobject JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1decimal(JNIEnv *env, jclass, jobject value) { + + duckdb_value val = value_buf_to_value(env, value); + if (env->ExceptionCheck()) { + return nullptr; + } + duckdb_decimal dec = duckdb_get_decimal(val); + jlong lower = static_cast(dec.value.lower); + jlong upper = static_cast(dec.value.upper); + jint scale = static_cast(dec.scale); + return env->CallStaticObjectMethod(J_HugeInt, J_HugeInt_toBigDecimal, lower, upper, scale); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_float + * Signature: (Ljava/nio/ByteBuffer;)F + */ +JNIEXPORT jfloat JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1float(JNIEnv *env, jclass, jobject value) { + + duckdb_value val = value_buf_to_value(env, value); + if (env->ExceptionCheck()) { + return static_cast(0); + } + float num = duckdb_get_float(val); + return static_cast(num); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_double + * Signature: (Ljava/nio/ByteBuffer;)D + */ +JNIEXPORT jdouble JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1double(JNIEnv *env, jclass, jobject value) { + + duckdb_value val = value_buf_to_value(env, value); + if (env->ExceptionCheck()) { + return static_cast(0); + } + double num = duckdb_get_double(val); + return static_cast(num); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_date + * Signature: (Ljava/nio/ByteBuffer;)I + */ +JNIEXPORT jint JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1date(JNIEnv *env, jclass, jobject value) { + duckdb_value val = value_buf_to_value(env, value); + if (env->ExceptionCheck()) { + return static_cast(0); + } + duckdb_date dt = duckdb_get_date(val); + return static_cast(dt.days); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_time + * Signature: (Ljava/nio/ByteBuffer;)J + */ +JNIEXPORT jlong JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1time(JNIEnv *env, jclass, jobject value) { + (void)env; + (void)value; + return 0; +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_time_ns + * Signature: (Ljava/nio/ByteBuffer;)J + */ +JNIEXPORT jlong JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1time_1ns(JNIEnv *env, jclass, jobject value) { + (void)env; + (void)value; + return 0; +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_time_tz + * Signature: (Ljava/nio/ByteBuffer;)J + */ +JNIEXPORT jlong JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1time_1tz(JNIEnv *env, jclass, jobject value) { + (void)env; + (void)value; + return 0; +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_timestamp + * Signature: (Ljava/nio/ByteBuffer;)J + */ +JNIEXPORT jlong JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1timestamp(JNIEnv *env, jclass, jobject value) { + + duckdb_value val = value_buf_to_value(env, value); + if (env->ExceptionCheck()) { + return static_cast(0); + } + + duckdb_timestamp ts = duckdb_get_timestamp(val); + return static_cast(ts.micros); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_timestamp_tz + * Signature: (Ljava/nio/ByteBuffer;)J + */ +JNIEXPORT jlong JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1timestamp_1tz(JNIEnv *env, jclass, jobject value) { + (void)env; + (void)value; + return 0; +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_timestamp_s + * Signature: (Ljava/nio/ByteBuffer;)J + */ +JNIEXPORT jlong JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1timestamp_1s(JNIEnv *env, jclass, jobject value) { + + duckdb_value val = value_buf_to_value(env, value); + if (env->ExceptionCheck()) { + return static_cast(0); + } + + duckdb_timestamp_s ts = duckdb_get_timestamp_s(val); + return static_cast(ts.seconds); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_timestamp_ms + * Signature: (Ljava/nio/ByteBuffer;)J + */ +JNIEXPORT jlong JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1timestamp_1ms(JNIEnv *env, jclass, jobject value) { + + duckdb_value val = value_buf_to_value(env, value); + if (env->ExceptionCheck()) { + return static_cast(0); + } + + duckdb_timestamp_ms ts = duckdb_get_timestamp_ms(val); + return static_cast(ts.millis); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_timestamp_ns + * Signature: (Ljava/nio/ByteBuffer;)J + */ +JNIEXPORT jlong JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1timestamp_1ns(JNIEnv *env, jclass, jobject value) { + + duckdb_value val = value_buf_to_value(env, value); + if (env->ExceptionCheck()) { + return static_cast(0); + } + + duckdb_timestamp_ns ts = duckdb_get_timestamp_ns(val); + return static_cast(ts.nanos); +} + +/* + * Class: org_duckdb_DuckDBBindings + * Method: duckdb_get_varchar + * Signature: (Ljava/nio/ByteBuffer;)[B + */ +JNIEXPORT jbyteArray JNICALL Java_org_duckdb_DuckDBBindings_duckdb_1get_1varchar(JNIEnv *env, jclass, jobject value) { + duckdb_value val = value_buf_to_value(env, value); + if (env->ExceptionCheck()) { + return nullptr; + } + + char *cstr = duckdb_get_varchar(val); + idx_t len = static_cast(std::strlen(cstr)); + jbyteArray result = make_jbyteArray(env, cstr, len); + + duckdb_free(cstr); + + return result; +} diff --git a/src/jni/holders.cpp b/src/jni/holders.cpp index a8b3661dc..7b11c67d0 100644 --- a/src/jni/holders.cpp +++ b/src/jni/holders.cpp @@ -1,5 +1,7 @@ #include "holders.hpp" +#include + ConnectionHolder *get_connection_ref(JNIEnv *env, jobject conn_ref_buf) { if (!conn_ref_buf) { throw duckdb::ConnectionException("Invalid connection buffer ref"); @@ -90,12 +92,14 @@ AttachedJNIEnv GlobalRefHolder::attach_current_thread() { JNIEnv *env = nullptr; auto env_status = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); if (env_status != JNI_OK && env_status != JNI_EDETACHED) { + std::cerr << "ERROR: vm->GetEnv() failure, status: " << env_status << std::endl; return AttachedJNIEnv(); } bool need_to_detach = false; if (env_status == JNI_EDETACHED) { auto attach_status = vm->AttachCurrentThread(reinterpret_cast(&env), nullptr); if (attach_status != JNI_OK || env == nullptr) { + std::cerr << "ERROR: vm->AttachCurrentThread() failure, status: " << attach_status << std::endl; return AttachedJNIEnv(); } need_to_detach = true; diff --git a/src/jni/refs.cpp b/src/jni/refs.cpp index e1a881d2c..46c142854 100644 --- a/src/jni/refs.cpp +++ b/src/jni/refs.cpp @@ -51,6 +51,8 @@ jmethodID J_BigDecimal_toPlainString; jmethodID J_BigDecimal_longValue; jfieldID J_HugeInt_lower; jfieldID J_HugeInt_upper; +jmethodID J_HugeInt_toBigInteger; +jmethodID J_HugeInt_toBigDecimal; jclass J_DuckResultSetMeta; jmethodID J_DuckResultSetMeta_init; @@ -121,6 +123,11 @@ jmethodID J_QueryProgress_init; jclass J_DuckDBScalarFunctionWrapper; jmethodID J_DuckDBScalarFunctionWrapper_execute; +jclass J_DuckDBTableFunctionWrapper; +jmethodID J_DuckDBTableFunctionWrapper_executeBind; +jmethodID J_DuckDBTableFunctionWrapper_executeGlobalInit; +jmethodID J_DuckDBTableFunctionWrapper_executeLocalInit; +jmethodID J_DuckDBTableFunctionWrapper_executeFunction; static std::vector global_refs; @@ -275,6 +282,8 @@ void create_refs(JNIEnv *env) { J_BigDecimal_longValue = get_method_id(env, J_BigDecimal, "longValue", "()J"); J_HugeInt_lower = get_field_id(env, J_HugeInt, "lower", "J"); J_HugeInt_upper = get_field_id(env, J_HugeInt, "upper", "J"); + J_HugeInt_toBigInteger = get_static_method_id(env, J_HugeInt, "toBigInteger", "(JJ)Ljava/math/BigInteger;"); + J_HugeInt_toBigDecimal = get_static_method_id(env, J_HugeInt, "toBigDecimal", "(JJI)Ljava/math/BigDecimal;"); J_DuckResultSetMeta = make_class_ref(env, "org/duckdb/DuckDBResultSetMetaData"); J_DuckResultSetMeta_init = env->GetMethodID(J_DuckResultSetMeta, "", @@ -316,6 +325,15 @@ void create_refs(JNIEnv *env) { J_DuckDBScalarFunctionWrapper_execute = get_method_id(env, J_DuckDBScalarFunctionWrapper, "execute", "(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;)V"); + J_DuckDBTableFunctionWrapper = make_class_ref(env, "org/duckdb/DuckDBTableFunctionWrapper"); + J_DuckDBTableFunctionWrapper_executeBind = + get_method_id(env, J_DuckDBTableFunctionWrapper, "executeBind", "(Ljava/nio/ByteBuffer;)V"); + J_DuckDBTableFunctionWrapper_executeGlobalInit = + get_method_id(env, J_DuckDBTableFunctionWrapper, "executeGlobalInit", "(Ljava/nio/ByteBuffer;)V"); + J_DuckDBTableFunctionWrapper_executeLocalInit = + get_method_id(env, J_DuckDBTableFunctionWrapper, "executeLocalInit", "(Ljava/nio/ByteBuffer;)V"); + J_DuckDBTableFunctionWrapper_executeFunction = get_method_id(env, J_DuckDBTableFunctionWrapper, "executeFunction", + "(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;)V"); } void delete_global_refs(JNIEnv *env) noexcept { @@ -324,6 +342,6 @@ void delete_global_refs(JNIEnv *env) noexcept { env->DeleteGlobalRef(rf); } } catch (const std::exception e) { - std::cout << "ERROR: delete_global_refs: " << e.what() << std::endl; + std::cerr << "ERROR: delete_global_refs: " << e.what() << std::endl; } } diff --git a/src/jni/refs.hpp b/src/jni/refs.hpp index fe830584d..1d55bbe3d 100644 --- a/src/jni/refs.hpp +++ b/src/jni/refs.hpp @@ -48,6 +48,8 @@ extern jmethodID J_BigDecimal_toPlainString; extern jmethodID J_BigDecimal_longValue; extern jfieldID J_HugeInt_lower; extern jfieldID J_HugeInt_upper; +extern jmethodID J_HugeInt_toBigInteger; +extern jmethodID J_HugeInt_toBigDecimal; extern jclass J_DuckResultSetMeta; extern jmethodID J_DuckResultSetMeta_init; @@ -118,6 +120,11 @@ extern jmethodID J_QueryProgress_init; extern jclass J_DuckDBScalarFunctionWrapper; extern jmethodID J_DuckDBScalarFunctionWrapper_execute; +extern jclass J_DuckDBTableFunctionWrapper; +extern jmethodID J_DuckDBTableFunctionWrapper_executeBind; +extern jmethodID J_DuckDBTableFunctionWrapper_executeGlobalInit; +extern jmethodID J_DuckDBTableFunctionWrapper_executeLocalInit; +extern jmethodID J_DuckDBTableFunctionWrapper_executeFunction; void create_refs(JNIEnv *env); diff --git a/src/jni/types.hpp b/src/jni/types.hpp index adbf36da9..c63fd8d51 100644 --- a/src/jni/types.hpp +++ b/src/jni/types.hpp @@ -1,5 +1,9 @@ #pragma once +extern "C" { +#include "duckdb.h" +} + #include "duckdb.hpp" #include diff --git a/src/main/java/org/duckdb/DuckDBBindings.java b/src/main/java/org/duckdb/DuckDBBindings.java index 3517fc13f..85826c721 100644 --- a/src/main/java/org/duckdb/DuckDBBindings.java +++ b/src/main/java/org/duckdb/DuckDBBindings.java @@ -1,7 +1,12 @@ package org.duckdb; +import java.math.BigDecimal; +import java.math.BigInteger; import java.nio.ByteBuffer; import java.sql.SQLException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; public class DuckDBBindings { @@ -43,6 +48,72 @@ public class DuckDBBindings { static native void duckdb_scalar_function_set_error(ByteBuffer functionInfo, byte[] error); + // table function bind + + static native void duckdb_bind_add_result_column(ByteBuffer bind_info, byte[] name, ByteBuffer logical_type); + + static native long duckdb_bind_get_parameter_count(ByteBuffer bind_info); + + static native ByteBuffer duckdb_bind_get_parameter(ByteBuffer bind_info, long index); + + static native ByteBuffer duckdb_bind_get_named_parameter(ByteBuffer bind_info, byte[] name); + + static native void duckdb_bind_set_bind_data(ByteBuffer bind_info, Object bind_data); + + static native void duckdb_bind_set_cardinality(ByteBuffer bind_info, long cardinality, boolean is_exact); + + static native void duckdb_bind_set_error(ByteBuffer bind_info, byte[] error); + + // table function init + + static native Object duckdb_init_get_bind_data(ByteBuffer init_info); + + static native void duckdb_init_set_init_data(ByteBuffer init_info, Object init_data); + + static native long duckdb_init_get_column_count(ByteBuffer init_info); + + static native long duckdb_init_get_column_index(ByteBuffer init_info, long column_index); + + static native void duckdb_init_set_max_threads(ByteBuffer init_info, long max_threads); + + static native void duckdb_init_set_error(ByteBuffer init_info, byte[] error); + + // table function + + static native ByteBuffer duckdb_create_table_function(); + + static native void duckdb_destroy_table_function(ByteBuffer table_function); + + static native void duckdb_table_function_set_name(ByteBuffer table_function, byte[] name); + + static native void duckdb_table_function_add_parameter(ByteBuffer table_function, ByteBuffer logical_type); + + static native void duckdb_table_function_add_named_parameter(ByteBuffer table_function, byte[] name, + ByteBuffer logical_type); + + static native void duckdb_table_function_set_extra_info(ByteBuffer table_function, + DuckDBTableFunctionWrapper callback); + + static native void duckdb_table_function_set_bind(ByteBuffer table_function); + + static native void duckdb_table_function_set_init(ByteBuffer table_function); + + static native void duckdb_table_function_set_local_init(ByteBuffer table_function); + + static native void duckdb_table_function_set_function(ByteBuffer table_function); + + static native void duckdb_table_function_supports_projection_pushdown(ByteBuffer table_function, boolean pushdown); + + static native int duckdb_register_table_function(ByteBuffer connection, ByteBuffer table_function); + + static native Object duckdb_function_get_bind_data(ByteBuffer table_function_info); + + static native Object duckdb_function_get_init_data(ByteBuffer table_function_info); + + static native Object duckdb_function_get_local_init_data(ByteBuffer table_function_info); + + static native void duckdb_function_set_error(ByteBuffer table_function_info, byte[] error); + // logical type static native ByteBuffer duckdb_create_logical_type(int duckdb_type); @@ -150,6 +221,64 @@ static native int duckdb_appender_create_ext(ByteBuffer connection, byte[] catal static native int duckdb_append_default_to_chunk(ByteBuffer appender, ByteBuffer chunk, long col, long row); + // value + + static native boolean duckdb_is_null_value(ByteBuffer value); + + static native int duckdb_get_value_type(ByteBuffer value); + + static native void duckdb_destroy_value(ByteBuffer value); + + static native boolean duckdb_get_bool(ByteBuffer value); + + static native byte duckdb_get_int8(ByteBuffer value); + + static native short duckdb_get_uint8(ByteBuffer value); + + static native short duckdb_get_int16(ByteBuffer value); + + static native int duckdb_get_uint16(ByteBuffer value); + + static native int duckdb_get_int32(ByteBuffer value); + + static native long duckdb_get_uint32(ByteBuffer value); + + static native long duckdb_get_int64(ByteBuffer value); + + static native long duckdb_get_uint64(ByteBuffer value); + + static native BigInteger duckdb_get_hugeint(ByteBuffer value); + + static native BigInteger duckdb_get_uhugeint(ByteBuffer value); + + static native BigInteger duckdb_get_bignum(ByteBuffer value); + + static native BigDecimal duckdb_get_decimal(ByteBuffer value); + + static native float duckdb_get_float(ByteBuffer value); + + static native double duckdb_get_double(ByteBuffer value); + + static native int duckdb_get_date(ByteBuffer value); + + static native long duckdb_get_time(ByteBuffer value); + + static native long duckdb_get_time_ns(ByteBuffer value); + + static native long duckdb_get_time_tz(ByteBuffer value); + + static native long duckdb_get_timestamp(ByteBuffer value); + + static native long duckdb_get_timestamp_tz(ByteBuffer value); + + static native long duckdb_get_timestamp_s(ByteBuffer value); + + static native long duckdb_get_timestamp_ms(ByteBuffer value); + + static native long duckdb_get_timestamp_ns(ByteBuffer value); + + static native byte[] duckdb_get_varchar(ByteBuffer value); + enum CAPIType { DUCKDB_TYPE_INVALID(0, 0), // bool diff --git a/src/main/java/org/duckdb/DuckDBDataChunkWriter.java b/src/main/java/org/duckdb/DuckDBDataChunkWriter.java new file mode 100644 index 000000000..59bb08ebe --- /dev/null +++ b/src/main/java/org/duckdb/DuckDBDataChunkWriter.java @@ -0,0 +1,48 @@ +package org.duckdb; + +import static org.duckdb.DuckDBBindings.*; + +import java.nio.ByteBuffer; + +public class DuckDBDataChunkWriter { + private final ByteBuffer chunkRef; + private final long rowCount; + private final long columnCount; + private final DuckDBWritableVector[] vectors; + + DuckDBDataChunkWriter(ByteBuffer chunkRef) { + if (chunkRef == null) { + throw new DuckDBFunctions.FunctionException("Invalid data chunk reference"); + } + this.chunkRef = chunkRef; + this.rowCount = duckdb_vector_size(); + this.columnCount = duckdb_data_chunk_get_column_count(chunkRef); + this.vectors = new DuckDBWritableVector[Math.toIntExact(columnCount)]; + + for (long columnIndex = 0; columnIndex < columnCount; columnIndex++) { + ByteBuffer vectorRef = duckdb_data_chunk_get_vector(chunkRef, columnIndex); + int arrayIndex = Math.toIntExact(columnIndex); + vectors[arrayIndex] = new DuckDBWritableVector(vectorRef, rowCount); + } + } + + public long capacity() { + return rowCount; + } + + void setSize(long size) { + duckdb_data_chunk_set_size(chunkRef, size); + } + + public long columnCount() { + return columnCount; + } + + public DuckDBWritableVector vector(long columnIndex) { + if (columnIndex < 0 || columnIndex >= columnCount) { + throw new IndexOutOfBoundsException("Column index out of bounds: " + columnIndex); + } + int arrayIndex = Math.toIntExact(columnIndex); + return vectors[arrayIndex]; + } +} diff --git a/src/main/java/org/duckdb/DuckDBDriver.java b/src/main/java/org/duckdb/DuckDBDriver.java index 3797e9ced..1b606600c 100644 --- a/src/main/java/org/duckdb/DuckDBDriver.java +++ b/src/main/java/org/duckdb/DuckDBDriver.java @@ -285,10 +285,12 @@ public static void clearFunctionsRegistry() { } } - static void registerFunction(RegisteredFunction function) { + static RegisteredFunction registerFunction(String name, DuckDBFunctions.Kind kind) { functionsRegistryLock.lock(); try { - functionsRegistry.add(function); + RegisteredFunction fun = new RegisteredFunction(name, kind); + functionsRegistry.add(fun); + return fun; } finally { functionsRegistryLock.unlock(); } diff --git a/src/main/java/org/duckdb/DuckDBFunctions.java b/src/main/java/org/duckdb/DuckDBFunctions.java index 201a1ffa4..1e132c22c 100644 --- a/src/main/java/org/duckdb/DuckDBFunctions.java +++ b/src/main/java/org/duckdb/DuckDBFunctions.java @@ -1,12 +1,11 @@ package org.duckdb; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.time.LocalDateTime; +import java.util.StringJoiner; public final class DuckDBFunctions { - public enum Kind { SCALAR } + public enum Kind { SCALAR, TABLE } private DuckDBFunctions() { } @@ -15,16 +14,8 @@ public static DuckDBScalarFunctionBuilder scalarFunction() throws SQLException { return new DuckDBScalarFunctionBuilder(); } - static RegisteredFunction createRegisteredFunction(String name, List parameterTypes, - List parameterColumnTypes, - DuckDBLogicalType returnType, DuckDBColumnType returnColumnType, - DuckDBScalarFunction function, DuckDBLogicalType varArgType, - boolean volatileFlag, boolean specialHandlingFlag, - boolean propagateNullsFlag) { - return new RegisteredFunction(name, Kind.SCALAR, Collections.unmodifiableList(new ArrayList<>(parameterTypes)), - Collections.unmodifiableList(new ArrayList<>(parameterColumnTypes)), returnType, - returnColumnType, function, varArgType, volatileFlag, specialHandlingFlag, - propagateNullsFlag); + public static DuckDBTableFunctionBuilder tableFunction() throws SQLException { + return new DuckDBTableFunctionBuilder(); } public static class FunctionException extends RuntimeException { @@ -34,6 +25,10 @@ public FunctionException(String message) { super(message); } + public FunctionException(Throwable cause) { + super(cause); + } + public FunctionException(String message, Throwable cause) { super(message, cause); } @@ -42,32 +37,12 @@ public FunctionException(String message, Throwable cause) { public static final class RegisteredFunction { private final String name; private final Kind functionKind; - private final List parameterTypes; - private final List parameterColumnTypes; - private final DuckDBLogicalType returnType; - private final DuckDBColumnType returnColumnType; - private final DuckDBScalarFunction function; - private final DuckDBLogicalType varArgType; - private final boolean volatileFlag; - private final boolean nullInNullOutFlag; - private final boolean propagateNullsFlag; + private final LocalDateTime registeredAt; - private RegisteredFunction(String name, Kind functionKind, List parameterTypes, - List parameterColumnTypes, DuckDBLogicalType returnType, - DuckDBColumnType returnColumnType, DuckDBScalarFunction function, - DuckDBLogicalType varArgType, boolean volatileFlag, boolean nullInNullOutFlag, - boolean propagateNullsFlag) { + RegisteredFunction(String name, Kind functionKind) { this.name = name; this.functionKind = functionKind; - this.parameterTypes = parameterTypes; - this.parameterColumnTypes = parameterColumnTypes; - this.returnType = returnType; - this.returnColumnType = returnColumnType; - this.function = function; - this.varArgType = varArgType; - this.volatileFlag = volatileFlag; - this.nullInNullOutFlag = nullInNullOutFlag; - this.propagateNullsFlag = propagateNullsFlag; + this.registeredAt = LocalDateTime.now(); } public String name() { @@ -78,44 +53,13 @@ public Kind functionKind() { return functionKind; } - public List parameterTypes() { - return parameterTypes; - } - - public List parameterColumnTypes() { - return parameterColumnTypes; - } - - public DuckDBLogicalType returnType() { - return returnType; - } - - public DuckDBColumnType returnColumnType() { - return returnColumnType; - } - - public DuckDBScalarFunction function() { - return function; - } - - public DuckDBLogicalType varArgType() { - return varArgType; - } - - public boolean isVolatile() { - return volatileFlag; - } - - public boolean isNullInNullOut() { - return nullInNullOutFlag; - } - - public boolean propagateNulls() { - return propagateNullsFlag; - } - - public boolean isScalar() { - return functionKind == Kind.SCALAR; + @Override + public String toString() { + return new StringJoiner(", ", RegisteredFunction.class.getSimpleName() + "[", "]") + .add("name='" + name + "'") + .add("functionKind=" + functionKind) + .add("registeredAt=" + registeredAt) + .toString(); } } } diff --git a/src/main/java/org/duckdb/DuckDBHugeInt.java b/src/main/java/org/duckdb/DuckDBHugeInt.java index eeca81f83..7165703ab 100644 --- a/src/main/java/org/duckdb/DuckDBHugeInt.java +++ b/src/main/java/org/duckdb/DuckDBHugeInt.java @@ -1,5 +1,8 @@ package org.duckdb; +import static org.duckdb.DuckDBVector.ULONG_MULTIPLIER; + +import java.math.BigDecimal; import java.math.BigInteger; import java.nio.ByteBuffer; import java.sql.SQLException; @@ -40,6 +43,13 @@ static BigInteger toUnsignedBigInteger(long lower, long upper) { return new BigInteger(1, bytes); } + static BigDecimal toBigDecimal(long lower, long upper, int scale) { + return new BigDecimal(upper) + .multiply(ULONG_MULTIPLIER) + .add(new BigDecimal(Long.toUnsignedString(lower))) + .scaleByPowerOfTen(scale * -1); + } + long lower() { return lower; } diff --git a/src/main/java/org/duckdb/DuckDBLogicalType.java b/src/main/java/org/duckdb/DuckDBLogicalType.java index 2d4f53b10..9da64f923 100644 --- a/src/main/java/org/duckdb/DuckDBLogicalType.java +++ b/src/main/java/org/duckdb/DuckDBLogicalType.java @@ -47,6 +47,8 @@ public static DuckDBLogicalType of(DuckDBColumnType type) throws SQLException { return createPrimitive(DUCKDB_TYPE_FLOAT); case DOUBLE: return createPrimitive(DUCKDB_TYPE_DOUBLE); + case DECIMAL: + return decimal(38, 18); case VARCHAR: return createPrimitive(DUCKDB_TYPE_VARCHAR); case DATE: @@ -62,7 +64,7 @@ public static DuckDBLogicalType of(DuckDBColumnType type) throws SQLException { case TIMESTAMP_WITH_TIME_ZONE: return createPrimitive(DUCKDB_TYPE_TIMESTAMP_TZ); default: - throw new SQLException("Unsupported logical type for scalar UDF registration: " + type); + throw new SQLException("Unsupported logical type for UDF registration: " + type); } } diff --git a/src/main/java/org/duckdb/DuckDBReadableVector.java b/src/main/java/org/duckdb/DuckDBReadableVector.java index 56fef2187..93a8c2ffb 100644 --- a/src/main/java/org/duckdb/DuckDBReadableVector.java +++ b/src/main/java/org/duckdb/DuckDBReadableVector.java @@ -2,6 +2,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.duckdb.DuckDBBindings.*; +import static org.duckdb.DuckDBTimestamp.localDateTimeFromTimestamp; import java.math.BigDecimal; import java.math.BigInteger; @@ -95,19 +96,6 @@ public byte getByte(long row, byte defaultValue) { return isNull(row) ? defaultValue : data.get(checkedRowIndex(row)); } - public short getShort(long row) { - requireType(DuckDBColumnType.SMALLINT); - if (isNull(row)) { - throw primitiveNullValue(DuckDBColumnType.SMALLINT, row); - } - return data.getShort(checkedByteOffset(row, Short.BYTES)); - } - - public short getShort(long row, short defaultValue) { - requireType(DuckDBColumnType.SMALLINT); - return isNull(row) ? defaultValue : data.getShort(checkedByteOffset(row, Short.BYTES)); - } - public short getUint8(long row) { requireType(DuckDBColumnType.UTINYINT); if (isNull(row)) { @@ -121,6 +109,19 @@ public short getUint8(long row, short defaultValue) { return isNull(row) ? defaultValue : (short) Byte.toUnsignedInt(data.get(checkedRowIndex(row))); } + public short getShort(long row) { + requireType(DuckDBColumnType.SMALLINT); + if (isNull(row)) { + throw primitiveNullValue(DuckDBColumnType.SMALLINT, row); + } + return data.getShort(checkedByteOffset(row, Short.BYTES)); + } + + public short getShort(long row, short defaultValue) { + requireType(DuckDBColumnType.SMALLINT); + return isNull(row) ? defaultValue : data.getShort(checkedByteOffset(row, Short.BYTES)); + } + public int getUint16(long row) { requireType(DuckDBColumnType.USMALLINT); if (isNull(row)) { @@ -173,6 +174,15 @@ public long getLong(long row, long defaultValue) { return isNull(row) ? defaultValue : data.getLong(checkedByteOffset(row, Long.BYTES)); } + public BigInteger getUint64(long row) { + requireType(DuckDBColumnType.UBIGINT); + if (isNull(row)) { + return null; + } + long value = data.getLong(checkedByteOffset(row, Long.BYTES)); + return unsignedLongToBigInteger(value); + } + public BigInteger getHugeInt(long row) { requireType(DuckDBColumnType.HUGEINT); if (isNull(row)) { @@ -195,15 +205,6 @@ public BigInteger getUHugeInt(long row) { return DuckDBHugeInt.toUnsignedBigInteger(lower, upper); } - public BigInteger getUint64(long row) { - requireType(DuckDBColumnType.UBIGINT); - if (isNull(row)) { - return null; - } - long value = data.getLong(checkedByteOffset(row, Long.BYTES)); - return unsignedLongToBigInteger(value); - } - public float getFloat(long row) { requireType(DuckDBColumnType.FLOAT); if (isNull(row)) { @@ -230,6 +231,32 @@ public double getDouble(long row, double defaultValue) { return isNull(row) ? defaultValue : data.getDouble(checkedByteOffset(row, Double.BYTES)); } + public BigDecimal getBigDecimal(long row) { + requireType(DuckDBColumnType.DECIMAL); + if (isNull(row)) { + return null; + } + switch (typeInfo.storageType) { + case DUCKDB_TYPE_SMALLINT: + return BigDecimal.valueOf(data.getShort(checkedByteOffset(row, Short.BYTES)), typeInfo.decimalMeta.scale); + case DUCKDB_TYPE_INTEGER: + return BigDecimal.valueOf(data.getInt(checkedByteOffset(row, Integer.BYTES)), typeInfo.decimalMeta.scale); + case DUCKDB_TYPE_BIGINT: + return BigDecimal.valueOf(data.getLong(checkedByteOffset(row, Long.BYTES)), typeInfo.decimalMeta.scale); + case DUCKDB_TYPE_HUGEINT: { + int offset = checkedByteOffset(row, typeInfo.widthBytes); + long lower = data.getLong(offset); + long upper = data.getLong(offset + Long.BYTES); + return new BigDecimal(upper) + .multiply(ULONG_MULTIPLIER) + .add(new BigDecimal(Long.toUnsignedString(lower))) + .scaleByPowerOfTen(typeInfo.decimalMeta.scale * -1); + } + default: + throw new FunctionException("Unsupported DECIMAL storage type: " + typeInfo.storageType); + } + } + public LocalDate getLocalDate(long row) { requireType(DuckDBColumnType.DATE); if (isNull(row)) { @@ -252,13 +279,13 @@ public LocalDateTime getLocalDateTime(long row) { try { switch (typeInfo.capiType) { case DUCKDB_TYPE_TIMESTAMP_S: - return DuckDBTimestamp.localDateTimeFromTimestamp(epochValue, ChronoUnit.SECONDS, null); + return localDateTimeFromTimestamp(epochValue, ChronoUnit.SECONDS); case DUCKDB_TYPE_TIMESTAMP_MS: - return DuckDBTimestamp.localDateTimeFromTimestamp(epochValue, ChronoUnit.MILLIS, null); + return localDateTimeFromTimestamp(epochValue, ChronoUnit.MILLIS); case DUCKDB_TYPE_TIMESTAMP: - return DuckDBTimestamp.localDateTimeFromTimestamp(epochValue, ChronoUnit.MICROS, null); + return localDateTimeFromTimestamp(epochValue, ChronoUnit.MICROS); case DUCKDB_TYPE_TIMESTAMP_NS: - return DuckDBTimestamp.localDateTimeFromTimestamp(epochValue, ChronoUnit.NANOS, null); + return localDateTimeFromTimestamp(epochValue, ChronoUnit.NANOS); case DUCKDB_TYPE_TIMESTAMP_TZ: return DuckDBTimestamp.localDateTimeFromTimestampWithTimezone(epochValue, ChronoUnit.MICROS, null); default: @@ -284,32 +311,6 @@ public OffsetDateTime getOffsetDateTime(long row) { return instant.atZone(ZoneId.systemDefault()).toOffsetDateTime(); } - public BigDecimal getBigDecimal(long row) { - requireType(DuckDBColumnType.DECIMAL); - if (isNull(row)) { - return null; - } - switch (typeInfo.storageType) { - case DUCKDB_TYPE_SMALLINT: - return BigDecimal.valueOf(data.getShort(checkedByteOffset(row, Short.BYTES)), typeInfo.decimalMeta.scale); - case DUCKDB_TYPE_INTEGER: - return BigDecimal.valueOf(data.getInt(checkedByteOffset(row, Integer.BYTES)), typeInfo.decimalMeta.scale); - case DUCKDB_TYPE_BIGINT: - return BigDecimal.valueOf(data.getLong(checkedByteOffset(row, Long.BYTES)), typeInfo.decimalMeta.scale); - case DUCKDB_TYPE_HUGEINT: { - int offset = checkedByteOffset(row, typeInfo.widthBytes); - long lower = data.getLong(offset); - long upper = data.getLong(offset + Long.BYTES); - return new BigDecimal(upper) - .multiply(ULONG_MULTIPLIER) - .add(new BigDecimal(Long.toUnsignedString(lower))) - .scaleByPowerOfTen(typeInfo.decimalMeta.scale * -1); - } - default: - throw new FunctionException("Unsupported DECIMAL storage type: " + typeInfo.storageType); - } - } - public String getString(long row) { requireType(DuckDBColumnType.VARCHAR); if (isNull(row)) { @@ -382,7 +383,7 @@ private static Instant instantFromEpoch(long value, ChronoUnit unit) { } } - private static BigInteger unsignedLongToBigInteger(long value) { + static BigInteger unsignedLongToBigInteger(long value) { if (value >= 0) { return BigInteger.valueOf(value); } diff --git a/src/main/java/org/duckdb/DuckDBScalarFunctionBuilder.java b/src/main/java/org/duckdb/DuckDBScalarFunctionBuilder.java index f4648d4fc..04bae1d9d 100644 --- a/src/main/java/org/duckdb/DuckDBScalarFunctionBuilder.java +++ b/src/main/java/org/duckdb/DuckDBScalarFunctionBuilder.java @@ -31,9 +31,7 @@ public final class DuckDBScalarFunctionBuilder implements AutoCloseable { private final List parameterTypes = new ArrayList<>(); private final List parameterColumnTypes = new ArrayList<>(); private final List> parameterJavaTypes = new ArrayList<>(); - private boolean volatileFlag; private boolean nullInNullOutFlag; - private boolean propagateNullsFlag; private boolean finalized; DuckDBScalarFunctionBuilder() throws SQLException { @@ -53,16 +51,21 @@ public DuckDBScalarFunctionBuilder withName(String name) throws SQLException { return this; } - public DuckDBScalarFunctionBuilder withReturnType(DuckDBLogicalType returnType) throws SQLException { + public DuckDBScalarFunctionBuilder withParameter(Class parameterType) throws SQLException { ensureNotFinalized(); - if (returnType == null) { - throw new SQLException("Return type cannot be null"); + if (parameterType == null) { + throw new SQLException("Parameter type cannot be null"); } - this.returnType = returnType; - this.returnColumnType = null; - this.returnJavaType = null; - duckdb_scalar_function_set_return_type(scalarFunctionRef, returnType.logicalTypeRef()); - return this; + DuckDBColumnType mappedType = DuckDBScalarFunctionAdapter.mapJavaClassToDuckDBType(parameterType); + return addMappedParameterType(mappedType, parameterType); + } + + public DuckDBScalarFunctionBuilder withParameter(DuckDBColumnType parameterType) throws SQLException { + ensureNotFinalized(); + if (parameterType == null) { + throw new SQLException("Parameter type cannot be null"); + } + return addMappedParameterType(parameterType, null); } public DuckDBScalarFunctionBuilder withParameter(DuckDBLogicalType parameterType) throws SQLException { @@ -77,70 +80,65 @@ public DuckDBScalarFunctionBuilder withParameter(DuckDBLogicalType parameterType return this; } - public DuckDBScalarFunctionBuilder withParameters(DuckDBLogicalType... parameterTypes) throws SQLException { + public DuckDBScalarFunctionBuilder withParameters(Class... parameterTypes) throws SQLException { ensureNotFinalized(); if (parameterTypes == null) { throw new SQLException("Parameter types cannot be null"); } - for (DuckDBLogicalType parameterType : parameterTypes) { + for (Class parameterType : parameterTypes) { withParameter(parameterType); } return this; } - public DuckDBScalarFunctionBuilder withReturnType(Class returnType) throws SQLException { + public DuckDBScalarFunctionBuilder withParameters(DuckDBColumnType... parameterTypes) throws SQLException { ensureNotFinalized(); - if (returnType == null) { - throw new SQLException("Return type cannot be null"); + if (parameterTypes == null) { + throw new SQLException("Parameter types cannot be null"); } - DuckDBColumnType mappedType = DuckDBScalarFunctionAdapter.mapJavaClassToDuckDBType(returnType); - return setMappedReturnType(mappedType, returnType); - } - - public DuckDBScalarFunctionBuilder withParameter(Class parameterType) throws SQLException { - ensureNotFinalized(); - if (parameterType == null) { - throw new SQLException("Parameter type cannot be null"); + for (DuckDBColumnType parameterType : parameterTypes) { + withParameter(parameterType); } - DuckDBColumnType mappedType = DuckDBScalarFunctionAdapter.mapJavaClassToDuckDBType(parameterType); - return addMappedParameterType(mappedType, parameterType); + return this; } - public DuckDBScalarFunctionBuilder withParameters(Class... parameterTypes) throws SQLException { + public DuckDBScalarFunctionBuilder withParameters(DuckDBLogicalType... parameterTypes) throws SQLException { ensureNotFinalized(); if (parameterTypes == null) { throw new SQLException("Parameter types cannot be null"); } - for (Class parameterType : parameterTypes) { + for (DuckDBLogicalType parameterType : parameterTypes) { withParameter(parameterType); } return this; } - public DuckDBScalarFunctionBuilder withReturnType(DuckDBColumnType returnType) throws SQLException { + public DuckDBScalarFunctionBuilder withReturnType(Class returnType) throws SQLException { ensureNotFinalized(); if (returnType == null) { throw new SQLException("Return type cannot be null"); } - return setMappedReturnType(returnType, null); + DuckDBColumnType mappedType = DuckDBScalarFunctionAdapter.mapJavaClassToDuckDBType(returnType); + return setMappedReturnType(mappedType, returnType); } - public DuckDBScalarFunctionBuilder withParameter(DuckDBColumnType parameterType) throws SQLException { + public DuckDBScalarFunctionBuilder withReturnType(DuckDBColumnType returnType) throws SQLException { ensureNotFinalized(); - if (parameterType == null) { - throw new SQLException("Parameter type cannot be null"); + if (returnType == null) { + throw new SQLException("Return type cannot be null"); } - return addMappedParameterType(parameterType, null); + return setMappedReturnType(returnType, null); } - public DuckDBScalarFunctionBuilder withParameters(DuckDBColumnType... parameterTypes) throws SQLException { + public DuckDBScalarFunctionBuilder withReturnType(DuckDBLogicalType returnType) throws SQLException { ensureNotFinalized(); - if (parameterTypes == null) { - throw new SQLException("Parameter types cannot be null"); - } - for (DuckDBColumnType parameterType : parameterTypes) { - withParameter(parameterType); + if (returnType == null) { + throw new SQLException("Return type cannot be null"); } + this.returnType = returnType; + this.returnColumnType = null; + this.returnJavaType = null; + duckdb_scalar_function_set_return_type(scalarFunctionRef, returnType.logicalTypeRef()); return this; } @@ -306,7 +304,6 @@ public DuckDBScalarFunctionBuilder withVarArgs(DuckDBLogicalType varArgType) thr public DuckDBScalarFunctionBuilder withVolatile() throws SQLException { ensureNotFinalized(); - this.volatileFlag = true; duckdb_scalar_function_set_volatile(scalarFunctionRef); return this; } @@ -343,11 +340,7 @@ public RegisteredFunction register(Connection connection) throws SQLException { if (status != 0) { throw new SQLException("Failed to register scalar function '" + functionName + "'"); } - RegisteredFunction registeredFunction = DuckDBFunctions.createRegisteredFunction( - functionName, parameterTypes, parameterColumnTypes, returnType, returnColumnType, callback, varArgType, - volatileFlag, nullInNullOutFlag, propagateNullsFlag); - DuckDBDriver.registerFunction(registeredFunction); - return registeredFunction; + return DuckDBDriver.registerFunction(functionName, DuckDBFunctions.Kind.SCALAR); } finally { connectionLock.unlock(); close(); @@ -356,10 +349,23 @@ public RegisteredFunction register(Connection connection) throws SQLException { @Override public void close() { + if (finalized) { + return; + } if (scalarFunctionRef != null) { duckdb_destroy_scalar_function(scalarFunctionRef); scalarFunctionRef = null; } + if (varArgType != null) { + varArgType.close(); + varArgType = null; + } + for (DuckDBLogicalType lt : parameterTypes) { + if (null != lt) { + lt.close(); + } + } + parameterTypes.clear(); finalized = true; } @@ -433,7 +439,6 @@ private DuckDBScalarFunctionBuilder addMappedParameterType(DuckDBColumnType mapp private DuckDBScalarFunctionBuilder setCallback(DuckDBScalarFunction function, boolean requiresNullPropagation) throws SQLException { this.callback = function; - this.propagateNullsFlag = requiresNullPropagation; DuckDBScalarFunctionWrapper wrapper = new DuckDBScalarFunctionWrapper(function); duckdb_scalar_function_set_extra_info(scalarFunctionRef, wrapper); duckdb_scalar_function_set_function(scalarFunctionRef); @@ -448,7 +453,6 @@ private void ensurePrimitiveCallbackCompatible(String callbackMethodName) throws private void enablePrimitiveNullPropagation() { nullInNullOutFlag = false; - propagateNullsFlag = true; } private void ensureUnaryPrimitiveSignature(DuckDBColumnType expectedType, String callbackMethodName) diff --git a/src/main/java/org/duckdb/DuckDBScalarFunctionWrapper.java b/src/main/java/org/duckdb/DuckDBScalarFunctionWrapper.java index 7ca90b7ff..e8877942f 100644 --- a/src/main/java/org/duckdb/DuckDBScalarFunctionWrapper.java +++ b/src/main/java/org/duckdb/DuckDBScalarFunctionWrapper.java @@ -1,6 +1,8 @@ package org.duckdb; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.duckdb.DuckDBBindings.duckdb_scalar_function_set_error; +import static org.duckdb.JdbcUtils.collectStackTrace; import java.nio.ByteBuffer; @@ -17,17 +19,8 @@ public void execute(ByteBuffer functionInfo, ByteBuffer inputChunk, ByteBuffer o DuckDBWritableVector outputWriter = new DuckDBWritableVector(outputVector, inputReader.rowCount()); function.apply(inputReader, outputWriter); } catch (Throwable throwable) { - reportError(functionInfo, throwable); + String trace = collectStackTrace(throwable); + duckdb_scalar_function_set_error(functionInfo, trace.getBytes(UTF_8)); } } - - // todo: stacktrace - private static void reportError(ByteBuffer functionInfo, Throwable throwable) { - String message = throwable.getMessage(); - String className = throwable.getClass().getName(); - String formatted = - message == null || message.isEmpty() ? className : String.format("%s: %s", className, message); - String error = "Java scalar function threw exception: " + formatted; - DuckDBBindings.duckdb_scalar_function_set_error(functionInfo, error.getBytes(UTF_8)); - } } diff --git a/src/main/java/org/duckdb/DuckDBTableFunction.java b/src/main/java/org/duckdb/DuckDBTableFunction.java new file mode 100644 index 000000000..a670d1ca1 --- /dev/null +++ b/src/main/java/org/duckdb/DuckDBTableFunction.java @@ -0,0 +1,14 @@ +package org.duckdb; + +public interface DuckDBTableFunction { + + B bind(DuckDBTableFunctionBindInfo info) throws Exception; + + G init(DuckDBTableFunctionInitInfo info) throws Exception; + + default L localInit(DuckDBTableFunctionInitInfo info) throws Exception { + return null; + } + + long apply(DuckDBTableFunctionCallInfo info, DuckDBDataChunkWriter output) throws Exception; +} diff --git a/src/main/java/org/duckdb/DuckDBTableFunctionBindInfo.java b/src/main/java/org/duckdb/DuckDBTableFunctionBindInfo.java new file mode 100644 index 000000000..fb5dc5afe --- /dev/null +++ b/src/main/java/org/duckdb/DuckDBTableFunctionBindInfo.java @@ -0,0 +1,97 @@ +package org.duckdb; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.duckdb.DuckDBBindings.*; + +import java.nio.ByteBuffer; +import java.sql.SQLException; +import java.util.concurrent.ConcurrentLinkedQueue; +import org.duckdb.DuckDBFunctions.FunctionException; + +public final class DuckDBTableFunctionBindInfo { + private final ByteBuffer bindInfoRef; + private final ConcurrentLinkedQueue accessedParameters = new ConcurrentLinkedQueue<>(); + + public DuckDBTableFunctionBindInfo(ByteBuffer bindInfoRef) { + this.bindInfoRef = bindInfoRef; + } + + public DuckDBTableFunctionBindInfo addResultColumn(String name, Class columnType) { + if (null == columnType) { + throw new FunctionException("Specified column type must be not null"); + } + DuckDBColumnType mappedType = null; + try { + mappedType = DuckDBScalarFunctionAdapter.mapJavaClassToDuckDBType(columnType); + } catch (SQLException e) { + throw new FunctionException(e); + } + return addResultColumn(name, mappedType); + } + + public DuckDBTableFunctionBindInfo addResultColumn(String name, DuckDBColumnType columnType) { + if (null == columnType) { + throw new FunctionException("Specified column type must be not null"); + } + try (DuckDBLogicalType logicalType = DuckDBLogicalType.of(columnType)) { + return addResultColumn(name, logicalType); + } catch (SQLException e) { + throw new FunctionException(e); + } + } + + public DuckDBTableFunctionBindInfo addResultColumn(String name, DuckDBLogicalType columnType) { + if (null == name || name.isEmpty()) { + throw new FunctionException("Specified column name must be not empty"); + } + try { + byte[] name_bytes = name.getBytes(UTF_8); + duckdb_bind_add_result_column(bindInfoRef, name_bytes, columnType.logicalTypeRef()); + return this; + } catch (SQLException e) { + throw new FunctionException(e); + } + } + + public DuckDBValue getParameter(long index) { + if (index < 0 || index >= parametersCount()) { + throw new IndexOutOfBoundsException("Parameter index out of bounds: " + index); + } + ByteBuffer valueRef = duckdb_bind_get_parameter(bindInfoRef, index); + if (valueRef == null) { + throw new FunctionException("Parameter at index " + index + " not found"); + } + DuckDBValue par = new DuckDBValue(valueRef); + accessedParameters.add(par); + return par; + } + + public DuckDBValue getNamedParameter(String name) { + if (name == null || name.trim().isEmpty()) { + throw new FunctionException("Parameter name cannot be empty"); + } + byte[] name_bytes = name.getBytes(UTF_8); + ByteBuffer valueRef = duckdb_bind_get_named_parameter(bindInfoRef, name_bytes); + if (valueRef == null) { + throw new FunctionException("Named parameter '" + name + "' not found"); + } + DuckDBValue par = new DuckDBValue(valueRef); + accessedParameters.add(par); + return par; + } + + void clearAccessedParameters() { + for (DuckDBValue par : accessedParameters) { + par.close(); + } + accessedParameters.clear(); + } + + public long parametersCount() { + return duckdb_bind_get_parameter_count(bindInfoRef); + } + + public void setCardinality(long cardinality, boolean isExact) { + duckdb_bind_set_cardinality(bindInfoRef, cardinality, isExact); + } +} diff --git a/src/main/java/org/duckdb/DuckDBTableFunctionBuilder.java b/src/main/java/org/duckdb/DuckDBTableFunctionBuilder.java new file mode 100644 index 000000000..1124ee513 --- /dev/null +++ b/src/main/java/org/duckdb/DuckDBTableFunctionBuilder.java @@ -0,0 +1,196 @@ +package org.duckdb; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.duckdb.DuckDBBindings.*; + +import java.nio.ByteBuffer; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.concurrent.locks.Lock; + +public class DuckDBTableFunctionBuilder implements AutoCloseable { + private ByteBuffer tableFunctionRef; + boolean finalized; + String functionName; + DuckDBTableFunction function; + + DuckDBTableFunctionBuilder() throws SQLException { + this.tableFunctionRef = duckdb_create_table_function(); + if (tableFunctionRef == null) { + throw new SQLException("Failed to create table function"); + } + } + + public DuckDBTableFunctionBuilder withName(String name) throws SQLException { + ensureNotFinalized(); + if (name == null || name.trim().isEmpty()) { + throw new SQLException("Function name cannot be null or empty"); + } + this.functionName = name; + duckdb_table_function_set_name(tableFunctionRef, name.getBytes(UTF_8)); + return this; + } + + public DuckDBTableFunctionBuilder withParameter(Class parameterType) throws SQLException { + ensureNotFinalized(); + if (parameterType == null) { + throw new SQLException("Parameter type cannot be null"); + } + DuckDBColumnType mappedType = DuckDBScalarFunctionAdapter.mapJavaClassToDuckDBType(parameterType); + return withParameter(mappedType); + } + + public DuckDBTableFunctionBuilder withParameter(DuckDBColumnType parameterType) throws SQLException { + ensureNotFinalized(); + if (parameterType == null) { + throw new SQLException("Parameter type cannot be null"); + } + try (DuckDBLogicalType logicalType = DuckDBLogicalType.of(parameterType)) { + return withParameter(logicalType); + } + } + + public DuckDBTableFunctionBuilder withParameter(DuckDBLogicalType parameterType) throws SQLException { + ensureNotFinalized(); + if (parameterType == null) { + throw new SQLException("Parameter type cannot be null"); + } + duckdb_table_function_add_parameter(tableFunctionRef, parameterType.logicalTypeRef()); + return this; + } + + public DuckDBTableFunctionBuilder withParameters(Class... parameterTypes) throws SQLException { + ensureNotFinalized(); + if (parameterTypes == null) { + throw new SQLException("Parameter types cannot be null"); + } + for (Class parameterType : parameterTypes) { + withParameter(parameterType); + } + return this; + } + + public DuckDBTableFunctionBuilder withParameters(DuckDBColumnType... parameterTypes) throws SQLException { + ensureNotFinalized(); + if (parameterTypes == null) { + throw new SQLException("Parameter types cannot be null"); + } + for (DuckDBColumnType parameterType : parameterTypes) { + withParameter(parameterType); + } + return this; + } + + public DuckDBTableFunctionBuilder withParameters(DuckDBLogicalType... parameterTypes) throws SQLException { + ensureNotFinalized(); + if (parameterTypes == null) { + throw new SQLException("Parameter types cannot be null"); + } + for (DuckDBLogicalType parameterType : parameterTypes) { + withParameter(parameterType); + } + return this; + } + + public DuckDBTableFunctionBuilder withNamedParameter(String name, Class parameterType) throws SQLException { + ensureNotFinalized(); + if (parameterType == null) { + throw new SQLException("Parameter type cannot be null"); + } + DuckDBColumnType mappedType = DuckDBScalarFunctionAdapter.mapJavaClassToDuckDBType(parameterType); + return withNamedParameter(name, mappedType); + } + + public DuckDBTableFunctionBuilder withNamedParameter(String name, DuckDBColumnType parameterType) + throws SQLException { + ensureNotFinalized(); + if (parameterType == null) { + throw new SQLException("Parameter type cannot be null"); + } + try (DuckDBLogicalType logicalType = DuckDBLogicalType.of(parameterType)) { + return withNamedParameter(name, logicalType); + } + } + + public DuckDBTableFunctionBuilder withNamedParameter(String name, DuckDBLogicalType parameterType) + throws SQLException { + ensureNotFinalized(); + if (name == null || name.trim().isEmpty()) { + throw new SQLException("Parameter name cannot be empty"); + } + if (parameterType == null) { + throw new SQLException("Parameter type cannot be null"); + } + byte[] nameBytes = name.getBytes(UTF_8); + duckdb_table_function_add_named_parameter(tableFunctionRef, nameBytes, parameterType.logicalTypeRef()); + return this; + } + + public DuckDBTableFunctionBuilder withProjectionPushdown() throws SQLException { + ensureNotFinalized(); + duckdb_table_function_supports_projection_pushdown(tableFunctionRef, true); + return this; + } + + public DuckDBTableFunctionBuilder withFunction(DuckDBTableFunction function) throws SQLException { + ensureNotFinalized(); + if (function == null) { + throw new SQLException("Table function object cannot be null"); + } + this.function = function; + return this; + } + + public DuckDBFunctions.RegisteredFunction register(Connection connection) throws SQLException { + ensureNotFinalized(); + if (connection == null) { + throw new SQLException("Connection cannot be null"); + } + if (functionName == null) { + throw new SQLException("Function name must be defined"); + } + if (function == null) { + throw new SQLException("Table function callback must be defined"); + } + + DuckDBTableFunctionWrapper wrapper = new DuckDBTableFunctionWrapper(function); + duckdb_table_function_set_extra_info(tableFunctionRef, wrapper); + duckdb_table_function_set_bind(tableFunctionRef); + duckdb_table_function_set_init(tableFunctionRef); + duckdb_table_function_set_local_init(tableFunctionRef); + duckdb_table_function_set_function(tableFunctionRef); + + DuckDBConnection duckConnection = connection.unwrap(DuckDBConnection.class); + Lock connectionLock = duckConnection.connRefLock; + connectionLock.lock(); + try { + duckConnection.checkOpen(); + int status = duckdb_register_table_function(duckConnection.connRef, tableFunctionRef); + if (status != 0) { + throw new SQLException("Failed to register table function '" + functionName + "'"); + } + return DuckDBDriver.registerFunction(functionName, DuckDBFunctions.Kind.TABLE); + } finally { + connectionLock.unlock(); + close(); + } + } + + @Override + public void close() { + if (finalized) { + return; + } + if (tableFunctionRef != null) { + duckdb_destroy_table_function(tableFunctionRef); + tableFunctionRef = null; + } + finalized = true; + } + + private void ensureNotFinalized() throws SQLException { + if (finalized || tableFunctionRef == null) { + throw new SQLException("Table function builder is already finalized"); + } + } +} diff --git a/src/main/java/org/duckdb/DuckDBTableFunctionCallInfo.java b/src/main/java/org/duckdb/DuckDBTableFunctionCallInfo.java new file mode 100644 index 000000000..40f6425e7 --- /dev/null +++ b/src/main/java/org/duckdb/DuckDBTableFunctionCallInfo.java @@ -0,0 +1,28 @@ +package org.duckdb; + +public final class DuckDBTableFunctionCallInfo { + private final Object bindData; + private final Object globalData; + private final Object localData; + + public DuckDBTableFunctionCallInfo(Object bindData, Object globalData, Object localData) { + this.bindData = bindData; + this.globalData = globalData; + this.localData = localData; + } + + @SuppressWarnings("unchecked") + public T getBindData() { + return (T) bindData; + } + + @SuppressWarnings("unchecked") + public T getInitData() { + return (T) globalData; + } + + @SuppressWarnings("unchecked") + public T getLocalInitData() { + return (T) localData; + } +} diff --git a/src/main/java/org/duckdb/DuckDBTableFunctionInitInfo.java b/src/main/java/org/duckdb/DuckDBTableFunctionInitInfo.java new file mode 100644 index 000000000..4bcce96fa --- /dev/null +++ b/src/main/java/org/duckdb/DuckDBTableFunctionInitInfo.java @@ -0,0 +1,30 @@ +package org.duckdb; + +import static org.duckdb.DuckDBBindings.*; + +import java.nio.ByteBuffer; + +public final class DuckDBTableFunctionInitInfo { + private final ByteBuffer initInfoRef; + + public DuckDBTableFunctionInitInfo(ByteBuffer initInfoRef) { + this.initInfoRef = initInfoRef; + } + + public void setMaxThreads(long maxThreads) { + duckdb_init_set_max_threads(initInfoRef, maxThreads); + } + + @SuppressWarnings("unchecked") + public T getBindData() { + return (T) duckdb_init_get_bind_data(initInfoRef); + } + + public long getColumnCount() { + return duckdb_init_get_column_count(initInfoRef); + } + + public long getColumnIndex(long columnIndex) { + return duckdb_init_get_column_index(initInfoRef, columnIndex); + } +} diff --git a/src/main/java/org/duckdb/DuckDBTableFunctionWrapper.java b/src/main/java/org/duckdb/DuckDBTableFunctionWrapper.java new file mode 100644 index 000000000..9c5fce682 --- /dev/null +++ b/src/main/java/org/duckdb/DuckDBTableFunctionWrapper.java @@ -0,0 +1,73 @@ +package org.duckdb; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.duckdb.DuckDBBindings.*; +import static org.duckdb.JdbcUtils.collectStackTrace; + +import java.nio.ByteBuffer; + +class DuckDBTableFunctionWrapper { + private final DuckDBTableFunction function; + + DuckDBTableFunctionWrapper(DuckDBTableFunction function) { + this.function = function; + } + + public void executeBind(ByteBuffer bindInfoRef) { + try { + DuckDBTableFunctionBindInfo bindInfo = new DuckDBTableFunctionBindInfo(bindInfoRef); + try { + Object bindData = function.bind(bindInfo); + if (null != bindData) { + duckdb_bind_set_bind_data(bindInfoRef, bindData); + } + } finally { + bindInfo.clearAccessedParameters(); + } + } catch (Throwable throwable) { + String trace = collectStackTrace(throwable); + duckdb_bind_set_error(bindInfoRef, trace.getBytes(UTF_8)); + } + } + + public void executeGlobalInit(ByteBuffer initInfoRef) { + try { + DuckDBTableFunctionInitInfo initInfo = new DuckDBTableFunctionInitInfo(initInfoRef); + Object globalData = function.init(initInfo); + if (null != globalData) { + duckdb_init_set_init_data(initInfoRef, globalData); + } + } catch (Throwable throwable) { + String trace = collectStackTrace(throwable); + duckdb_init_set_error(initInfoRef, trace.getBytes(UTF_8)); + } + } + + public void executeLocalInit(ByteBuffer initInfoRef) { + try { + DuckDBTableFunctionInitInfo initInfo = new DuckDBTableFunctionInitInfo(initInfoRef); + Object localData = function.localInit(initInfo); + if (null != localData) { + duckdb_init_set_init_data(initInfoRef, localData); + } + } catch (Throwable throwable) { + String trace = collectStackTrace(throwable); + duckdb_init_set_error(initInfoRef, trace.getBytes(UTF_8)); + } + } + + public void executeFunction(ByteBuffer tableFunctionInfoRef, ByteBuffer outputChunkRef) { + try { + Object bindData = DuckDBBindings.duckdb_function_get_bind_data(tableFunctionInfoRef); + Object globalData = DuckDBBindings.duckdb_function_get_init_data(tableFunctionInfoRef); + Object localData = DuckDBBindings.duckdb_function_get_local_init_data(tableFunctionInfoRef); + DuckDBTableFunctionCallInfo info = new DuckDBTableFunctionCallInfo(bindData, globalData, localData); + DuckDBDataChunkWriter writer = new DuckDBDataChunkWriter(outputChunkRef); + long recordsWritten = function.apply(info, writer); + writer.setSize(recordsWritten); + } catch (Throwable throwable) { + String trace = collectStackTrace(throwable); + duckdb_function_set_error(tableFunctionInfoRef, trace.getBytes(UTF_8)); + } + } +} diff --git a/src/main/java/org/duckdb/DuckDBTimestamp.java b/src/main/java/org/duckdb/DuckDBTimestamp.java index 920a2ea40..6fb36068e 100644 --- a/src/main/java/org/duckdb/DuckDBTimestamp.java +++ b/src/main/java/org/duckdb/DuckDBTimestamp.java @@ -61,6 +61,10 @@ public static LocalDateTime localDateTimeFromTimestampWithTimezone(long value, C return LocalDateTime.ofInstant(instant, zoneId); } + static LocalDateTime localDateTimeFromTimestamp(long value, ChronoUnit unit) throws SQLException { + return localDateTimeFromTimestamp(value, unit, null); + } + public static LocalDateTime localDateTimeFromTimestamp(long value, ChronoUnit unit, ZoneId zoneIdNullable) throws SQLException { Instant instant = createInstant(value, unit); diff --git a/src/main/java/org/duckdb/DuckDBValue.java b/src/main/java/org/duckdb/DuckDBValue.java new file mode 100644 index 000000000..a3003e99c --- /dev/null +++ b/src/main/java/org/duckdb/DuckDBValue.java @@ -0,0 +1,313 @@ +package org.duckdb; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.duckdb.DuckDBBindings.*; +import static org.duckdb.DuckDBBindings.CAPIType.*; +import static org.duckdb.DuckDBReadableVector.unsignedLongToBigInteger; +import static org.duckdb.DuckDBTimestamp.localDateTimeFromTimestamp; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.sql.Date; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.*; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import org.duckdb.DuckDBFunctions.FunctionException; + +public final class DuckDBValue implements AutoCloseable { + private ByteBuffer valueRef; + + DuckDBValue(ByteBuffer valueRef) { + this.valueRef = valueRef; + } + + public boolean isNull() { + checkOpen(); + return duckdb_is_null_value(valueRef); + } + + public boolean getBoolean() { + checkType(DUCKDB_TYPE_BOOLEAN); + if (isNull()) { + throw new FunctionException("Parameter value is NULL"); + } + return duckdb_get_bool(valueRef); + } + + public boolean getBoolean(boolean defaultValue) { + checkType(DUCKDB_TYPE_BOOLEAN); + if (isNull()) { + return defaultValue; + } + return duckdb_get_bool(valueRef); + } + + public byte getByte() { + checkType(DUCKDB_TYPE_TINYINT); + if (isNull()) { + throw new FunctionException("Parameter value is NULL"); + } + return duckdb_get_int8(valueRef); + } + + public byte getByte(byte defaultValue) { + checkType(DUCKDB_TYPE_TINYINT); + if (isNull()) { + return defaultValue; + } + return duckdb_get_int8(valueRef); + } + + public short getUint8() { + checkType(DUCKDB_TYPE_UTINYINT); + if (isNull()) { + throw new FunctionException("Parameter value is NULL"); + } + return duckdb_get_uint8(valueRef); + } + + public short getUint8(short defaultValue) { + checkType(DUCKDB_TYPE_UTINYINT); + if (isNull()) { + return defaultValue; + } + return duckdb_get_uint8(valueRef); + } + + public short getShort() { + checkType(DUCKDB_TYPE_SMALLINT); + if (isNull()) { + throw new FunctionException("Parameter value is NULL"); + } + return duckdb_get_int16(valueRef); + } + + public short getShort(short defaultValue) { + checkType(DUCKDB_TYPE_SMALLINT); + if (isNull()) { + return defaultValue; + } + return duckdb_get_int16(valueRef); + } + + public int getUint16() { + checkType(DUCKDB_TYPE_USMALLINT); + if (isNull()) { + throw new FunctionException("Parameter value is NULL"); + } + return duckdb_get_uint16(valueRef); + } + + public int getUint16(int defaultValue) { + checkType(DUCKDB_TYPE_USMALLINT); + if (isNull()) { + return defaultValue; + } + return duckdb_get_uint16(valueRef); + } + + public int getInt() { + checkType(DUCKDB_TYPE_INTEGER); + if (isNull()) { + throw new FunctionException("Parameter value is NULL"); + } + return duckdb_get_int32(valueRef); + } + + public int getInt(int defaultValue) { + checkType(DUCKDB_TYPE_INTEGER); + if (isNull()) { + return defaultValue; + } + return duckdb_get_int32(valueRef); + } + + public long getUint32() { + checkType(DUCKDB_TYPE_UINTEGER); + if (isNull()) { + throw new FunctionException("Parameter value is NULL"); + } + return duckdb_get_uint32(valueRef); + } + + public long getUint32(long defaultValue) { + checkType(DUCKDB_TYPE_UINTEGER); + if (isNull()) { + return defaultValue; + } + return duckdb_get_uint32(valueRef); + } + + public long getLong() { + checkType(DUCKDB_TYPE_BIGINT); + if (isNull()) { + throw new FunctionException("Parameter value is NULL"); + } + return duckdb_get_int64(valueRef); + } + + public long getLong(long defaultValue) { + checkType(DUCKDB_TYPE_BIGINT); + if (isNull()) { + return defaultValue; + } + return duckdb_get_int64(valueRef); + } + + public BigInteger getUint64() { + checkType(DUCKDB_TYPE_UBIGINT); + if (isNull()) { + return null; + } + long unsigned = duckdb_get_uint64(valueRef); + return unsignedLongToBigInteger(unsigned); + } + + public BigInteger getHugeInt() { + checkType(DUCKDB_TYPE_HUGEINT); + if (isNull()) { + return null; + } + return duckdb_get_hugeint(valueRef); + } + + public BigInteger getUHugeInt() { + checkType(DUCKDB_TYPE_UHUGEINT); + if (isNull()) { + return null; + } + return duckdb_get_uhugeint(valueRef); + } + + public float getFloat() { + checkType(DUCKDB_TYPE_FLOAT); + if (isNull()) { + throw new FunctionException("Parameter value is NULL"); + } + return duckdb_get_float(valueRef); + } + + public float getFloat(float defaultValue) { + checkType(DUCKDB_TYPE_FLOAT); + if (isNull()) { + return defaultValue; + } + return duckdb_get_float(valueRef); + } + + public double getDouble() { + checkType(DUCKDB_TYPE_DOUBLE); + if (isNull()) { + throw new FunctionException("Parameter value is NULL"); + } + return duckdb_get_double(valueRef); + } + + public double getDouble(double defaultValue) { + checkType(DUCKDB_TYPE_DOUBLE); + if (isNull()) { + return defaultValue; + } + return duckdb_get_double(valueRef); + } + + public BigDecimal getBigDecimal() { + checkType(DUCKDB_TYPE_DECIMAL); + if (isNull()) { + return null; + } + return duckdb_get_decimal(valueRef); + } + + public LocalDate getLocalDate() { + checkType(DUCKDB_TYPE_DATE); + if (isNull()) { + return null; + } + int days = duckdb_get_date(valueRef); + return LocalDate.ofEpochDay(days); + } + + public LocalDateTime getLocalDateTime() { + CAPIType ctype = checkTypes(DUCKDB_TYPE_TIMESTAMP, DUCKDB_TYPE_TIMESTAMP_NS, DUCKDB_TYPE_TIMESTAMP_MS, + DUCKDB_TYPE_TIMESTAMP_S); + + try { + switch (ctype) { + case DUCKDB_TYPE_TIMESTAMP_S: + long seconds = duckdb_get_timestamp_s(valueRef); + return localDateTimeFromTimestamp(seconds, ChronoUnit.SECONDS); + case DUCKDB_TYPE_TIMESTAMP_MS: + long millis = duckdb_get_timestamp_ms(valueRef); + return localDateTimeFromTimestamp(millis, ChronoUnit.MILLIS); + case DUCKDB_TYPE_TIMESTAMP: + long micros = duckdb_get_timestamp(valueRef); + return localDateTimeFromTimestamp(micros, ChronoUnit.MICROS); + case DUCKDB_TYPE_TIMESTAMP_NS: + long nanos = duckdb_get_timestamp_ns(valueRef); + return localDateTimeFromTimestamp(nanos, ChronoUnit.NANOS); + default: + throw new FunctionException("Invalid type: " + ctype); + } + } catch (SQLException e) { + throw new FunctionException(e); + } + } + + public String getString() { + checkType(DUCKDB_TYPE_VARCHAR); + byte[] bytes = duckdb_get_varchar(valueRef); + if (bytes == null) { + return null; + } + return new String(bytes, UTF_8); + } + + @Override + public void close() { + if (null != valueRef) { + duckdb_destroy_value(valueRef); + valueRef = null; + } + } + + private void checkOpen() { + if (null == valueRef) { + throw new FunctionException( + "Parameter native value was closed, it is only available during the 'bind()' invocation"); + } + } + + private void checkType(CAPIType expected) { + checkOpen(); + try { + int typeId = duckdb_get_value_type(valueRef); + CAPIType ctype = CAPIType.capiTypeFromTypeId(typeId); + if (ctype != expected) { + throw new FunctionException("Invalid value type, expected: " + expected + ", actual: " + ctype); + } + } catch (SQLException e) { + throw new FunctionException(e); + } + } + + private CAPIType checkTypes(CAPIType... expected) { + checkOpen(); + try { + int typeId = duckdb_get_value_type(valueRef); + CAPIType ctype = CAPIType.capiTypeFromTypeId(typeId); + for (CAPIType ex : expected) { + if (ctype == ex) { + return ctype; + } + } + throw new FunctionException("Invalid value type, expected one of: " + Arrays.toString(expected) + + ", actual: " + ctype); + } catch (SQLException e) { + throw new FunctionException(e); + } + } +} diff --git a/src/main/java/org/duckdb/DuckDBVector.java b/src/main/java/org/duckdb/DuckDBVector.java index df8441b13..940545f69 100644 --- a/src/main/java/org/duckdb/DuckDBVector.java +++ b/src/main/java/org/duckdb/DuckDBVector.java @@ -26,7 +26,7 @@ class DuckDBVector { // Constant to construct BigDecimals from hugeint_t - private final static BigDecimal ULONG_MULTIPLIER = new BigDecimal("18446744073709551616"); + final static BigDecimal ULONG_MULTIPLIER = new BigDecimal("18446744073709551616"); private final static DateTimeFormatter ERA_FORMAT = new DateTimeFormatterBuilder() .appendValue(ChronoField.YEAR_OF_ERA) diff --git a/src/main/java/org/duckdb/JdbcUtils.java b/src/main/java/org/duckdb/JdbcUtils.java index 823ad8a82..4426d90e6 100644 --- a/src/main/java/org/duckdb/JdbcUtils.java +++ b/src/main/java/org/duckdb/JdbcUtils.java @@ -3,6 +3,8 @@ import static org.duckdb.DuckDBDriver.DUCKDB_URL_PREFIX; import static org.duckdb.DuckDBDriver.MEMORY_DB; +import java.io.PrintWriter; +import java.io.StringWriter; import java.sql.SQLException; import java.util.Properties; @@ -105,4 +107,11 @@ static void closeQuietly(AutoCloseable closeable) { // suppress } } + + static String collectStackTrace(Throwable throwable) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + throwable.printStackTrace(pw); + return sw.toString(); + } } diff --git a/src/test/java/org/duckdb/TestDuckDBJDBC.java b/src/test/java/org/duckdb/TestDuckDBJDBC.java index 88051efdb..c37afb098 100644 --- a/src/test/java/org/duckdb/TestDuckDBJDBC.java +++ b/src/test/java/org/duckdb/TestDuckDBJDBC.java @@ -2243,13 +2243,13 @@ public static void main(String[] args) throws Exception { Class clazz = Class.forName("org.duckdb." + arg1); statusCode = runTests(new String[0], clazz); } else { - statusCode = - runTests(args, TestDuckDBJDBC.class, TestAppender.class, TestAppenderCollection.class, - TestAppenderCollection2D.class, TestAppenderComposite.class, TestSingleValueAppender.class, - TestBatch.class, TestBindings.class, TestClosure.class, TestExtensionTypes.class, - TestMetadata.class, TestNoLib.class, TestScalarFunctions.class, - /* TestSpatial.class,*/ TestParameterMetadata.class, TestPrepare.class, TestResults.class, - TestSessionInit.class, TestTimestamp.class, TestVariant.class); + statusCode = runTests(args, TestDuckDBJDBC.class, TestAppender.class, TestAppenderCollection.class, + TestAppenderCollection2D.class, TestAppenderComposite.class, + TestSingleValueAppender.class, TestBatch.class, TestBindings.class, TestClosure.class, + TestExtensionTypes.class, TestMetadata.class, TestNoLib.class, + /* TestSpatial.class,*/ TestParameterMetadata.class, TestPrepare.class, + TestResults.class, TestScalarFunctions.class, TestSessionInit.class, + TestTableFunctions.class, TestTimestamp.class, TestVariant.class); } System.exit(statusCode); } diff --git a/src/test/java/org/duckdb/TestScalarFunctions.java b/src/test/java/org/duckdb/TestScalarFunctions.java index f63da607a..aeee72d04 100644 --- a/src/test/java/org/duckdb/TestScalarFunctions.java +++ b/src/test/java/org/duckdb/TestScalarFunctions.java @@ -207,13 +207,6 @@ public static void test_register_scalar_function_builder() throws Exception { }) .register(conn); assertEquals(function.name(), "java_add_int_builder"); - assertEquals(function.parameterTypes().size(), 1); - assertEquals(function.parameterTypes().get(0), intType); - assertEquals(function.returnType(), intType); - assertEquals(function.varArgType(), null); - assertEquals(function.isVolatile(), false); - assertEquals(function.isNullInNullOut(), false); - assertEquals(function.propagateNulls(), false); try (ResultSet rs = stmt.executeQuery("SELECT java_add_int_builder(v) FROM (VALUES (1), (NULL), (41)) t(v)")) { @@ -265,13 +258,7 @@ public static void test_register_scalar_function_builder_returns_detached_metada assertTrue(message.contains("already finalized")); assertEquals(function.name(), "java_add_int_detached"); - assertEquals(function.parameterColumnTypes().size(), 1); - assertEquals(function.parameterColumnTypes().get(0), DuckDBColumnType.INTEGER); - assertEquals(function.returnColumnType(), DuckDBColumnType.INTEGER); - assertNotNull(function.function()); assertEquals(function.functionKind(), DuckDBFunctions.Kind.SCALAR); - assertTrue(function.isScalar()); - assertEquals(function.propagateNulls(), false); try (ResultSet rs = stmt.executeQuery("SELECT java_add_int_detached(v) FROM (VALUES (1), (NULL), (41)) t(v)")) { @@ -300,7 +287,6 @@ public static void test_register_scalar_function_registry_records_registered_fun assertEquals(registeredFunctions.size(), 1); assertEquals(registeredFunctions.get(0), function); assertEquals(registeredFunctions.get(0).functionKind(), DuckDBFunctions.Kind.SCALAR); - assertTrue(registeredFunctions.get(0).isScalar()); try (ResultSet rs = stmt.executeQuery("SELECT java_registry_recorded(41)")) { assertTrue(rs.next()); @@ -433,10 +419,6 @@ public static void test_register_scalar_function_builder_varargs_and_flags() thr input.stream().forEach(row -> output.setInt(row, sumNonNullIntColumns(input, row))); }) .register(conn); - assertEquals(function.varArgType(), intType); - assertEquals(function.isVolatile(), true); - assertEquals(function.isNullInNullOut(), false); - assertEquals(function.propagateNulls(), false); try (ResultSet rs = stmt.executeQuery("SELECT java_sum_varargs_builder(1, 2, 3), java_sum_varargs_builder(5)")) { @@ -466,11 +448,6 @@ public static void test_register_scalar_function_builder_column_type_overloads() }); }) .register(conn); - assertEquals(function.parameterColumnTypes().size(), 1); - assertEquals(function.parameterColumnTypes().get(0), DuckDBColumnType.INTEGER); - assertEquals(function.parameterTypes().get(0), null); - assertEquals(function.returnColumnType(), DuckDBColumnType.INTEGER); - assertEquals(function.returnType(), null); try (ResultSet rs = stmt.executeQuery( "SELECT java_add_int_builder_col_type(v) FROM (VALUES (1), (NULL), (41)) t(v)")) { @@ -540,7 +517,6 @@ public static void test_register_scalar_function_builder_java_function_propagate .withReturnType(Integer.class) .withFunction((Integer x) -> x == null ? 99 : x + 1) .register(conn); - assertEquals(function.propagateNulls(), false); try (ResultSet rs = stmt.executeQuery( "SELECT java_add_int_function_nullable(v) FROM (VALUES (1), (NULL), (41)) t(v)")) { @@ -593,7 +569,6 @@ public static void test_register_scalar_function_builder_java_bifunction_propaga .withFunction( (Integer left, Integer right) -> (left == null ? 0 : left) + (right == null ? 0 : right)) .register(conn); - assertEquals(function.propagateNulls(), false); try ( ResultSet rs = stmt.executeQuery( @@ -620,7 +595,6 @@ public static void test_register_scalar_function_builder_with_int_function() thr .withReturnType(Integer.class) .withIntFunction(x -> x + 1) .register(conn); - assertEquals(function.propagateNulls(), true); try (ResultSet rs = stmt.executeQuery( "SELECT java_add_int_with_int_function(v) FROM (VALUES (1), (NULL), (41)) t(v)")) { @@ -645,7 +619,6 @@ public static void test_register_scalar_function_builder_with_int_binary_functio .withReturnType(Integer.class) .withIntFunction((left, right) -> left + right) .register(conn); - assertEquals(function.propagateNulls(), true); try (ResultSet rs = stmt.executeQuery("SELECT java_add_int_with_int_binary_function(a, b) " + "FROM (VALUES (1, 2), (NULL, 2), (39, 3), (5, NULL)) t(a, b)")) { @@ -671,7 +644,6 @@ public static void test_register_scalar_function_builder_with_double_function() .withReturnType(Double.class) .withDoubleFunction(x -> x + 0.5d) .register(conn); - assertEquals(function.propagateNulls(), true); try (ResultSet rs = stmt.executeQuery( "SELECT java_add_double_with_double_function(v) FROM (VALUES (41.5), (NULL), (-2.5)) t(v)")) { @@ -696,7 +668,6 @@ public static void test_register_scalar_function_builder_with_double_binary_func .withReturnType(Double.class) .withDoubleFunction((left, right) -> left + right) .register(conn); - assertEquals(function.propagateNulls(), true); try (ResultSet rs = stmt.executeQuery("SELECT java_add_double_with_double_binary_function(a, b) FROM " @@ -723,7 +694,6 @@ public static void test_register_scalar_function_builder_with_long_function() th .withReturnType(Long.class) .withLongFunction(x -> x + 3) .register(conn); - assertEquals(function.propagateNulls(), true); try ( ResultSet rs = stmt.executeQuery( @@ -749,8 +719,6 @@ public static void test_register_scalar_function_builder_with_long_binary_functi .withReturnType(Long.class) .withLongFunction((left, right) -> left + right) .register(conn); - assertEquals(function.propagateNulls(), true); - try (ResultSet rs = stmt.executeQuery("SELECT java_add_long_with_long_binary_function(a, b) " + "FROM (VALUES (1::BIGINT, 2::BIGINT), (NULL, 2::BIGINT), " + "(39::BIGINT, 3::BIGINT), (5::BIGINT, NULL)) t(a, b)")) { @@ -779,8 +747,7 @@ public static void test_register_scalar_function_builder_java_function_class_cas String message = assertThrows(() -> { stmt.executeQuery("SELECT java_invalid_cast_function(1)"); }, SQLException.class); - assertTrue(message.contains("Java scalar function threw exception")); - assertTrue(message.contains("ClassCastException")); + assertTrue(message.contains(DuckDBScalarFunctionAdapter.class.getSimpleName())); } } @@ -819,8 +786,7 @@ public static void test_register_scalar_function_builder_java_supplier_class_cas String message = assertThrows(() -> { stmt.executeQuery("SELECT java_invalid_supplier_cast()"); }, SQLException.class); - assertTrue(message.contains("Java scalar function threw exception")); - assertTrue(message.contains("ClassCastException")); + assertTrue(message.contains(DuckDBScalarFunctionAdapter.class.getSimpleName())); } } @@ -1544,9 +1510,7 @@ public static void test_register_scalar_function_exception_propagation() throws .register(conn); String message = assertThrows(() -> { stmt.executeQuery("SELECT java_throws_exception(1)"); }, SQLException.class); - assertTrue(message.contains("Java scalar function threw exception")); - assertTrue(message.contains("IllegalStateException")); - assertTrue(message.contains("boom")); + assertTrue(message.contains(TestScalarFunctions.class.getSimpleName())); } } diff --git a/src/test/java/org/duckdb/TestTableFunctions.java b/src/test/java/org/duckdb/TestTableFunctions.java new file mode 100644 index 000000000..ead6d1a04 --- /dev/null +++ b/src/test/java/org/duckdb/TestTableFunctions.java @@ -0,0 +1,339 @@ +package org.duckdb; + +import static org.duckdb.TestDuckDBJDBC.JDBC_URL; +import static org.duckdb.test.Assertions.*; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.Statement; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import org.duckdb.DuckDBFunctions.FunctionException; + +public class TestTableFunctions { + + public static void test_table_function_basic() throws Exception { + try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement()) { + DuckDBFunctions.tableFunction() + .withName("java_table_basic") + .withParameter(int.class) + .withNamedParameter("param1", String.class) + .withFunction(new DuckDBTableFunction() { + @Override + public Integer bind(DuckDBTableFunctionBindInfo info) throws Exception { + info.addResultColumn("col1", Integer.TYPE).addResultColumn("col2", String.class); + DuckDBValue param = info.getParameter(0); + assertThrows(param::getBoolean, FunctionException.class); + DuckDBValue namedParam = info.getNamedParameter("param1"); + assertEquals(namedParam.getString(), "foobar"); + return param.getInt(); + } + + @Override + public AtomicBoolean init(DuckDBTableFunctionInitInfo info) throws Exception { + info.setMaxThreads(1); + return new AtomicBoolean(false); + } + + @Override + public long apply(DuckDBTableFunctionCallInfo info, DuckDBDataChunkWriter output) throws Exception { + Integer bindData = info.getBindData(); + AtomicBoolean done = info.getInitData(); + if (done.get()) { + return 0; + } + output.vector(0).setInt(0, bindData); + output.vector(1).setString(0, "foo"); + output.vector(0).setNull(1); + output.vector(1).setString(1, "bar"); + done.set(true); + return 2; + } + }) + .register(conn); + try (ResultSet rs = stmt.executeQuery("FROM java_table_basic(42, param1='foobar')")) { + assertTrue(rs.next()); + assertEquals(rs.getInt(1), 42); + assertEquals(rs.getString(2), "foo"); + assertTrue(rs.next()); + assertNull(rs.getObject(1)); + assertTrue(rs.wasNull()); + assertEquals(rs.getString(2), "bar"); + assertFalse(rs.next()); + } + } + } + + public static void test_table_function_long_result() throws Exception { + long count = (1 << 16) + 7; + try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement()) { + DuckDBFunctions.tableFunction() + .withName("java_table_long_result") + .withFunction(new DuckDBTableFunction() { + @Override + public Object bind(DuckDBTableFunctionBindInfo info) throws Exception { + info.addResultColumn("col1", Long.TYPE).addResultColumn("col2", String.class); + return null; + } + + @Override + public AtomicLong init(DuckDBTableFunctionInitInfo info) throws Exception { + info.setMaxThreads(1); + return new AtomicLong(count); + } + + @Override + public long apply(DuckDBTableFunctionCallInfo info, DuckDBDataChunkWriter output) throws Exception { + AtomicLong remainingHolder = info.getInitData(); + long remaining = remainingHolder.get(); + if (remaining <= 0) { + return 0; + } + + DuckDBWritableVector vec1 = output.vector(0); + DuckDBWritableVector vec2 = output.vector(1); + + long limit = Math.min(remaining, output.capacity()); + long row = 0; + for (; row < limit; row++) { + long val = remaining - row; + vec1.setLong(row, val); + vec2.setString(row, val + "foo"); + } + remainingHolder.set(remaining - row); + return row; + } + }) + .register(conn); + try (ResultSet rs = stmt.executeQuery("FROM java_table_long_result()")) { + long fetched = 0; + while (rs.next()) { + long val = count - fetched; + assertEquals(rs.getLong(1), val); + assertEquals(rs.getString(2), val + "foo"); + fetched++; + } + assertEquals(fetched, count); + } + } + } + + public static void test_table_function_projection_pushdown() throws Exception { + try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement()) { + DuckDBFunctions.tableFunction() + .withName("java_table_projection_pushdown") + .withProjectionPushdown() + .withFunction(new DuckDBTableFunction() { + @Override + public String bind(DuckDBTableFunctionBindInfo info) throws Exception { + info.addResultColumn("col1", Integer.TYPE).addResultColumn("col2", String.class); + return "foobar"; + } + + @Override + public AtomicBoolean init(DuckDBTableFunctionInitInfo info) throws Exception { + info.setMaxThreads(1); + assertEquals(info.getColumnCount(), (long) 2); + assertEquals(info.getColumnIndex(0), (long) 0); + return new AtomicBoolean(false); + } + + @Override + public long apply(DuckDBTableFunctionCallInfo info, DuckDBDataChunkWriter output) throws Exception { + String bindData = info.getBindData(); + assertEquals(bindData, "foobar"); + AtomicBoolean done = info.getInitData(); + if (done.get()) { + return 0; + } + output.vector(0).setInt(0, 41); + output.vector(1).setString(0, "foo"); + output.vector(0).setInt(1, 42); + output.vector(1).setString(1, "bar"); + done.set(true); + return 2; + } + }) + .register(conn); + try (ResultSet rs = stmt.executeQuery("FROM java_table_projection_pushdown()")) { + assertTrue(rs.next()); + assertEquals(rs.getInt(1), 41); + assertEquals(rs.getString(2), "foo"); + assertTrue(rs.next()); + assertEquals(rs.getInt(1), 42); + assertEquals(rs.getString(2), "bar"); + assertFalse(rs.next()); + } + } + } + + static class ParametersTestBindData { + final boolean boolParam; + final byte byteParam; + final short uint8Param; + final short shortParam; + final int uint16Param; + final int intParam; + final long uint32Param; + final long longParam; + final BigInteger uint64Param; + final BigInteger hugeIntParam; + final BigInteger uhugeIntParam; + final float floatParam; + final double doubleParam; + final BigDecimal decimalParam; + final LocalDate localDateParam; + final LocalDateTime localDateTimeParam; + final String stringParam; + + ParametersTestBindData(boolean boolParam, byte byteParam, short uint8Param, short shortParam, int uint16Param, + int intParam, long uint32Param, long longParam, BigInteger uint64Param, + BigInteger hugeIntParam, BigInteger uhugeIntParam, float floatParam, double doubleParam, + BigDecimal decimalParam, LocalDate localDateParam, LocalDateTime localDateTimeParam, + String stringParam) { + this.boolParam = boolParam; + this.byteParam = byteParam; + this.uint8Param = uint8Param; + this.shortParam = shortParam; + this.uint16Param = uint16Param; + this.intParam = intParam; + this.uint32Param = uint32Param; + this.longParam = longParam; + this.uint64Param = uint64Param; + this.hugeIntParam = hugeIntParam; + this.uhugeIntParam = uhugeIntParam; + this.floatParam = floatParam; + this.doubleParam = doubleParam; + this.decimalParam = decimalParam; + this.localDateParam = localDateParam; + this.localDateTimeParam = localDateTimeParam; + this.stringParam = stringParam; + } + } + + public static void test_table_function_parameter_types() throws Exception { + try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement()) { + DuckDBFunctions.tableFunction() + .withName("java_table_parameter_types") + .withParameters(DuckDBColumnType.BOOLEAN, DuckDBColumnType.TINYINT, DuckDBColumnType.UTINYINT, + DuckDBColumnType.SMALLINT, DuckDBColumnType.USMALLINT, DuckDBColumnType.INTEGER, + DuckDBColumnType.UINTEGER, DuckDBColumnType.BIGINT, DuckDBColumnType.UBIGINT, + DuckDBColumnType.HUGEINT, DuckDBColumnType.UHUGEINT, DuckDBColumnType.FLOAT, + DuckDBColumnType.DOUBLE, DuckDBColumnType.DECIMAL, DuckDBColumnType.DATE, + DuckDBColumnType.TIMESTAMP, DuckDBColumnType.VARCHAR) + .withFunction(new DuckDBTableFunction() { + @Override + public ParametersTestBindData bind(DuckDBTableFunctionBindInfo info) throws Exception { + info.addResultColumn("par0", DuckDBColumnType.BOOLEAN) + .addResultColumn("par1", DuckDBColumnType.TINYINT) + .addResultColumn("par2", DuckDBColumnType.UTINYINT) + .addResultColumn("par3", DuckDBColumnType.SMALLINT) + .addResultColumn("par4", DuckDBColumnType.USMALLINT) + .addResultColumn("par5", DuckDBColumnType.INTEGER) + .addResultColumn("par6", DuckDBColumnType.UINTEGER) + .addResultColumn("par7", DuckDBColumnType.BIGINT) + .addResultColumn("par8", DuckDBColumnType.UBIGINT) + .addResultColumn("par9", DuckDBColumnType.HUGEINT) + .addResultColumn("par10", DuckDBColumnType.UHUGEINT) + .addResultColumn("par11", DuckDBColumnType.FLOAT) + .addResultColumn("par12", DuckDBColumnType.DOUBLE) + .addResultColumn("par13", DuckDBColumnType.DECIMAL) + .addResultColumn("par14", DuckDBColumnType.DATE) + .addResultColumn("par15", DuckDBColumnType.TIMESTAMP) + .addResultColumn("par16", DuckDBColumnType.VARCHAR); + return new ParametersTestBindData( + info.getParameter(0).getBoolean(), info.getParameter(1).getByte(), + info.getParameter(2).getUint8(), info.getParameter(3).getShort(), + info.getParameter(4).getUint16(), info.getParameter(5).getInt(), + info.getParameter(6).getUint32(), info.getParameter(7).getLong(), + info.getParameter(8).getUint64(), info.getParameter(9).getHugeInt(), + info.getParameter(10).getUHugeInt(), info.getParameter(11).getFloat(), + info.getParameter(12).getDouble(), info.getParameter(13).getBigDecimal(), + info.getParameter(14).getLocalDate(), info.getParameter(15).getLocalDateTime(), + info.getParameter(16).getString()); + } + + @Override + public AtomicBoolean init(DuckDBTableFunctionInitInfo info) throws Exception { + info.setMaxThreads(1); + return new AtomicBoolean(false); + } + + @Override + public long apply(DuckDBTableFunctionCallInfo info, DuckDBDataChunkWriter output) throws Exception { + ParametersTestBindData bindData = info.getBindData(); + AtomicBoolean done = info.getInitData(); + if (done.get()) { + return 0; + } + + output.vector(0).setBoolean(0, bindData.boolParam); + output.vector(1).setByte(0, bindData.byteParam); + output.vector(2).setUint8(0, bindData.uint8Param); + output.vector(3).setShort(0, bindData.shortParam); + output.vector(4).setUint16(0, bindData.uint16Param); + output.vector(5).setInt(0, bindData.intParam); + output.vector(6).setUint32(0, bindData.uint32Param); + output.vector(7).setLong(0, bindData.longParam); + output.vector(8).setUint64(0, bindData.uint64Param); + output.vector(9).setHugeInt(0, bindData.hugeIntParam); + output.vector(10).setUHugeInt(0, bindData.uhugeIntParam); + output.vector(11).setFloat(0, bindData.floatParam); + output.vector(12).setDouble(0, bindData.doubleParam); + output.vector(13).setBigDecimal(0, bindData.decimalParam); + output.vector(14).setDate(0, bindData.localDateParam); + output.vector(15).setTimestamp(0, bindData.localDateTimeParam); + output.vector(16).setString(0, bindData.stringParam); + + done.set(true); + return 1; + } + }) + .register(conn); + try (DuckDBResultSet rs = (DuckDBResultSet) stmt.executeQuery("FROM java_table_parameter_types(" + + "TRUE::BOOLEAN," + + "41::TINYINT," + + "42::UTINYINT," + + "43::SMALLINT," + + "44::USMALLINT," + + "45::INTEGER," + + "46::UINTEGER," + + "47::BIGINT," + + "48::UBIGINT," + + "49::HUGEINT," + + "50::UHUGEINT," + + "51::FLOAT," + + "52::DOUBLE," + + "53::DECIMAL," + + "'2020-12-31'::DATE," + + "'2020-12-31 23:58:59'::TIMESTAMP," + + "'foobar'::VARCHAR" + + ")")) { + assertTrue(rs.next()); + assertTrue(rs.getBoolean(1)); + assertEquals(rs.getByte(2), (byte) 41); + assertEquals(rs.getShort(3), (short) 42); + assertEquals(rs.getShort(4), (short) 43); + assertEquals(rs.getInt(5), 44); + assertEquals(rs.getInt(6), 45); + assertEquals(rs.getLong(7), (long) 46); + assertEquals(rs.getLong(8), (long) 47); + assertEquals(rs.getHugeint(9), BigInteger.valueOf(48)); + assertEquals(rs.getHugeint(10), BigInteger.valueOf(49)); + assertEquals(rs.getHugeint(11), BigInteger.valueOf(50)); + assertEquals(rs.getFloat(12), (float) 51); + assertEquals(rs.getDouble(13), (double) 52); + assertEquals(rs.getBigDecimal(14), BigDecimal.valueOf(53).setScale(18)); + assertEquals(rs.getObject(15, LocalDate.class), LocalDate.of(2020, 12, 31)); + assertEquals(rs.getObject(16, LocalDateTime.class), LocalDateTime.of(2020, 12, 31, 23, 58, 59)); + assertEquals(rs.getString(17), "foobar"); + assertFalse(rs.next()); + } + } + } +}