This is an automated email from the ASF dual-hosted git repository.
lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git
The following commit(s) were added to refs/heads/main by this push:
new d2d32eddc feat(java/driver/jni): add JNI bindings to native driver
manager (#2401)
d2d32eddc is described below
commit d2d32eddcaae522e536fc53e4c838ae4b634417d
Author: David Li <[email protected]>
AuthorDate: Tue Apr 8 11:00:32 2025 +0900
feat(java/driver/jni): add JNI bindings to native driver manager (#2401)
Fixes #2027.
---
CONTRIBUTING.md | 37 ++
c/driver_manager/adbc_driver_manager.cc | 2 +-
ci/conda_env_python.txt | 3 +
.../java_jni_build.sh} | 43 ++-
compose.yaml | 2 +-
java/CMakeLists.txt | 61 ++++
java/driver/flight-sql/pom.xml | 1 -
java/driver/jni/CMakeLists.txt | 44 +++
java/driver/{flight-sql => jni}/pom.xml | 72 ++--
java/driver/jni/src/main/cpp/jni_wrapper.cc | 373 +++++++++++++++++++++
.../arrow/adbc/driver/jni/JniConnection.java | 52 +++
.../apache/arrow/adbc/driver/jni/JniDatabase.java | 45 +++
.../apache/arrow/adbc/driver/jni/JniDriver.java | 54 +++
.../arrow/adbc/driver/jni/JniDriverFactory.java | 30 ++
.../apache/arrow/adbc/driver/jni/JniStatement.java | 68 ++++
.../arrow/adbc/driver/jni/impl/JniLoader.java | 100 ++++++
.../arrow/adbc/driver/jni/impl/NativeAdbc.java | 40 +++
.../driver/jni/impl/NativeConnectionHandle.java | 33 ++
.../adbc/driver/jni/impl/NativeDatabaseHandle.java | 33 ++
.../arrow/adbc/driver/jni/impl/NativeHandle.java | 69 ++++
.../adbc/driver/jni/impl/NativeQueryResult.java | 36 ++
.../driver/jni/impl/NativeStatementHandle.java | 33 ++
.../apache/arrow/adbc/driver/jni/package-info.java | 18 +
...ache.arrow.adbc.drivermanager.AdbcDriverFactory | 16 +-
.../arrow/adbc/driver/jni/JniDriverTest.java | 126 +++++++
java/pom.xml | 13 +
26 files changed, 1329 insertions(+), 75 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c9cbd0afb..7608a051d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -316,6 +316,43 @@ mvn install -Perrorprone
[checker-framework]: https://checkerframework.org/
[errorprone]: https://errorprone.info/
+#### JNI
+
+To build the JNI bridge, the native components must be built.
+
+```
+# Build the driver manager
+export ADBC_BUILD_STATIC=ON
+export ADBC_BUILD_TESTS=OFF
+export ADBC_USE_ASAN=OFF
+export ADBC_USE_UBSAN=OFF
+export BUILD_ALL=OFF
+export BUILD_DRIVER_MANAGER=ON
+export BUILD_DRIVER_SQLITE=ON
+./ci/scripts/cpp_build.sh $(pwd) $(pwd)/build $(pwd)/local
+
+# Build the JNI libraries
+./ci/scripts/java_jni_build.sh $(pwd) $(pwd)/java/build $(pwd)/local
+```
+
+Now build the Java code with the `jni` Maven profile enabled. To run tests,
+the SQLite driver must also be present in (DY)LD_LIBRARY_PATH.
+
+```
+export LD_LIBRARY_PATH=$(pwd)/local/lib
+pushd java
+mvn install -Pjni
+popd
+```
+
+This will build a JAR with native libraries for a single platform. If the
+native libraries are built for multiple platforms, they can all be copied to
+appropriate paths in the resources directory to build a single JAR that works
+across multiple platforms.
+
+You can also build and test in IntelliJ; simply edit the run/test
+configuration to add `LD_LIBRARY_PATH` to the environment.
+
### Python
Python libraries are managed with [setuptools][setuptools]. See
diff --git a/c/driver_manager/adbc_driver_manager.cc
b/c/driver_manager/adbc_driver_manager.cc
index 0ce173a88..5e4a8b6df 100644
--- a/c/driver_manager/adbc_driver_manager.cc
+++ b/c/driver_manager/adbc_driver_manager.cc
@@ -173,7 +173,7 @@ struct ManagedLibrary {
void* handle = dlopen(library, RTLD_NOW | RTLD_LOCAL);
if (!handle) {
- error_message = "[DriverManager] dlopen() failed: ";
+ error_message = "dlopen() failed: ";
error_message += dlerror();
// If applicable, append the shared library prefix/extension and
diff --git a/ci/conda_env_python.txt b/ci/conda_env_python.txt
index d0d4de475..c251dd8d1 100644
--- a/ci/conda_env_python.txt
+++ b/ci/conda_env_python.txt
@@ -17,6 +17,9 @@
Cython
importlib-resources
+# libxml2 broke ABI compatibility
+# https://github.com/conda-forge/arrow-cpp-feedstock/issues/1740
+libxml2 <2.14.0
# nodejs is required by pyright
nodejs >=13.0.0
pandas
diff --git a/ci/conda_env_python.txt b/ci/scripts/java_jni_build.sh
old mode 100644
new mode 100755
similarity index 57%
copy from ci/conda_env_python.txt
copy to ci/scripts/java_jni_build.sh
index d0d4de475..6ccfb183a
--- a/ci/conda_env_python.txt
+++ b/ci/scripts/java_jni_build.sh
@@ -1,3 +1,4 @@
+#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
@@ -15,18 +16,30 @@
# specific language governing permissions and limitations
# under the License.
-Cython
-importlib-resources
-# nodejs is required by pyright
-nodejs >=13.0.0
-pandas
-pip
-pyarrow-all
-pyright
-pytest
-setuptools
-
-# For integration testing
-polars
-protobuf
-python-duckdb
+set -ex
+
+: ${ADBC_CMAKE_ARGS:=""}
+: ${CMAKE_BUILD_TYPE:=Debug}
+
+main() {
+ local -r source_dir=${1}
+ local -r build_dir=${2}
+ local -r install_dir=${3}
+
+ mkdir -p "${build_dir}"
+ pushd "${build_dir}"
+
+ set -x
+ cmake "${source_dir}/java" \
+ ${ADBC_CMAKE_ARGS} \
+ -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" \
+
-DCMAKE_INSTALL_PREFIX="${source_dir}/java/driver/jni/src/main/resources/" \
+ -DCMAKE_PREFIX_PATH="${install_dir}/lib/cmake/"
+ set +x
+
+ cmake --build . --target install -j
+
+ popd
+}
+
+main "$@"
diff --git a/compose.yaml b/compose.yaml
index a337bd5e6..9b7ca4c5d 100644
--- a/compose.yaml
+++ b/compose.yaml
@@ -67,7 +67,7 @@ services:
command: |
/bin/bash -c 'git config --global --add safe.directory /adbc && source
/opt/conda/etc/profile.d/conda.sh && mamba create -y -n adbc -c conda-forge go
--file /adbc/ci/conda_env_cpp.txt --file /adbc/ci/conda_env_docs.txt --file
/adbc/ci/conda_env_java.txt --file /adbc/ci/conda_env_python.txt && conda
activate adbc && /adbc/ci/scripts/cpp_build.sh /adbc /adbc/build &&
/adbc/ci/scripts/go_build.sh /adbc /adbc/build &&
/adbc/ci/scripts/python_build.sh /adbc /adbc/build && /adbc/ci/scrip [...]
- ############################ Java JARs ######################################
+ ################################### Java ###################################
java-dist:
image: ${ARCH}/maven:${MAVEN}-jdk-${JDK}
diff --git a/java/CMakeLists.txt b/java/CMakeLists.txt
new file mode 100644
index 000000000..e4ae54ec3
--- /dev/null
+++ b/java/CMakeLists.txt
@@ -0,0 +1,61 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+cmake_minimum_required(VERSION 3.16)
+message(STATUS "Building using CMake version: ${CMAKE_VERSION}")
+
+# find_package() uses <PackageName>_ROOT variables.
+# https://cmake.org/cmake/help/latest/policy/CMP0074.html
+if(POLICY CMP0074)
+ cmake_policy(SET CMP0074 NEW)
+endif()
+
+project(adbc-java)
+
+if("${CMAKE_CXX_STANDARD}" STREQUAL "")
+ set(CMAKE_CXX_STANDARD 17)
+endif()
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+include(GNUInstallDirs)
+
+# Dependencies
+
+find_package(Java REQUIRED)
+find_package(JNI REQUIRED)
+
+include(UseJava)
+
+# ADBC_ARCH_DIR is derived from the architecture. The user can override this
+# variable if auto-detection fails.
+if("${ADBC_ARCH_DIR}" STREQUAL "")
+ if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "aarch64")
+ set(ADBC_ARCH_DIR "aarch_64")
+ elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "i386")
+ set(ADBC_ARCH_DIR "x86_64")
+ elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "arm64")
+ set(ADBC_ARCH_DIR "aarch_64")
+ elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "AMD64")
+ set(ADBC_ARCH_DIR "x86_64")
+ elseif("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64")
+ set(ADBC_ARCH_DIR "x86_64")
+ else()
+ message(FATAL_ERROR "Unsupported architecture: ${CMAKE_SYSTEM_PROCESSOR}")
+ endif()
+endif()
+
+add_subdirectory(driver/jni)
diff --git a/java/driver/flight-sql/pom.xml b/java/driver/flight-sql/pom.xml
index c84897497..b7b909761 100644
--- a/java/driver/flight-sql/pom.xml
+++ b/java/driver/flight-sql/pom.xml
@@ -35,7 +35,6 @@
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
- <!-- Latest version still supporting Java 8 -->
<version>3.2.0</version>
</dependency>
<dependency>
diff --git a/java/driver/jni/CMakeLists.txt b/java/driver/jni/CMakeLists.txt
new file mode 100644
index 000000000..b33d61879
--- /dev/null
+++ b/java/driver/jni/CMakeLists.txt
@@ -0,0 +1,44 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+find_package(AdbcDriverManager)
+
+add_custom_command(OUTPUT
${CMAKE_CURRENT_SOURCE_DIR}/target/headers/org_apache_arrow_adbc_driver_jni_impl_NativeAdbc.h
+ COMMENT "Generate JNI headers"
+ # Force Maven to actually re-run the command
+ COMMAND rm -rf ${CMAKE_CURRENT_SOURCE_DIR}/target/headers
+ ${CMAKE_CURRENT_SOURCE_DIR}/target/maven-status
+ COMMAND mvn --file ${CMAKE_CURRENT_SOURCE_DIR}/../..
-Pjni,javah
+ compile --also-make --projects :adbc-driver-jni
+ DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeAdbc.java
+)
+
+add_library(adbc_driver_jni SHARED
+ src/main/cpp/jni_wrapper.cc
+
${CMAKE_CURRENT_SOURCE_DIR}/target/headers/org_apache_arrow_adbc_driver_jni_impl_NativeAdbc.h
+)
+target_include_directories(adbc_driver_jni
+ PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/target/headers)
+target_link_libraries(adbc_driver_jni JNI::JNI
+ AdbcDriverManager::adbc_driver_manager_static)
+
+set(ADBC_DRIVER_JNI_C_LIBDIR "adbc_driver_jni/${ADBC_ARCH_DIR}")
+set(ADBC_DRIVER_JNI_C_BINDIR "adbc_driver_jni/${ADBC_ARCH_DIR}")
+
+install(TARGETS adbc_driver_jni
+ LIBRARY DESTINATION ${ADBC_DRIVER_JNI_C_LIBDIR}
+ RUNTIME DESTINATION ${ADBC_DRIVER_JNI_C_BINDIR})
diff --git a/java/driver/flight-sql/pom.xml b/java/driver/jni/pom.xml
similarity index 68%
copy from java/driver/flight-sql/pom.xml
copy to java/driver/jni/pom.xml
index c84897497..2afb4fd8b 100644
--- a/java/driver/flight-sql/pom.xml
+++ b/java/driver/jni/pom.xml
@@ -22,44 +22,33 @@
<parent>
<groupId>org.apache.arrow.adbc</groupId>
<artifactId>arrow-adbc-java-root</artifactId>
- <version>0.18.0-SNAPSHOT</version>
+ <version>0.17.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
- <artifactId>adbc-driver-flight-sql</artifactId>
+ <artifactId>adbc-driver-jni</artifactId>
<packaging>jar</packaging>
- <name>Arrow ADBC Driver Flight SQL</name>
- <description>An ADBC driver wrapping Flight SQL.</description>
+ <name>Arrow ADBC Driver Native</name>
+ <description>An ADBC driver wrapping the native driver manager.</description>
<dependencies>
- <dependency>
- <groupId>com.github.ben-manes.caffeine</groupId>
- <artifactId>caffeine</artifactId>
- <!-- Latest version still supporting Java 8 -->
- <version>3.2.0</version>
- </dependency>
- <dependency>
- <groupId>com.google.protobuf</groupId>
- <artifactId>protobuf-java</artifactId>
- <version>4.30.2</version>
- </dependency>
-
<!-- Arrow -->
<dependency>
<groupId>org.apache.arrow</groupId>
- <artifactId>arrow-memory-core</artifactId>
+ <artifactId>arrow-c-data</artifactId>
</dependency>
<dependency>
<groupId>org.apache.arrow</groupId>
- <artifactId>arrow-vector</artifactId>
+ <artifactId>arrow-memory-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.arrow</groupId>
- <artifactId>flight-core</artifactId>
+ <artifactId>arrow-memory-netty</artifactId>
+ <scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.arrow</groupId>
- <artifactId>flight-sql</artifactId>
+ <artifactId>arrow-vector</artifactId>
</dependency>
<dependency>
@@ -70,16 +59,6 @@
<groupId>org.apache.arrow.adbc</groupId>
<artifactId>adbc-driver-manager</artifactId>
</dependency>
- <dependency>
- <groupId>org.apache.arrow.adbc</groupId>
- <artifactId>adbc-sql</artifactId>
- </dependency>
-
- <!-- Helpers for mapping Arrow types to ANSI SQL types and building test
servers -->
- <dependency>
- <groupId>org.apache.arrow</groupId>
- <artifactId>flight-sql-jdbc-core</artifactId>
- </dependency>
<!-- Static analysis and linting -->
<dependency>
@@ -98,18 +77,6 @@
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
- <dependency>
- <groupId>org.junit.vintage</groupId>
- <artifactId>junit-vintage-engine</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.arrow</groupId>
- <artifactId>flight-sql-jdbc-core</artifactId>
- <version>${dep.arrow.version}</version>
- <classifier>tests</classifier>
- <scope>test</scope>
- </dependency>
</dependencies>
<build>
@@ -125,4 +92,25 @@
</plugin>
</plugins>
</build>
+
+ <profiles>
+ <profile>
+ <!-- Invoked by CMake to generate headers. -->
+ <!-- You must first build the project (without JNI enabled). -->
+ <id>javah</id>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <compilerArgs>
+ <arg>-h</arg>
+ <arg>${project.basedir}/target/headers</arg>
+ </compilerArgs>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
</project>
diff --git a/java/driver/jni/src/main/cpp/jni_wrapper.cc
b/java/driver/jni/src/main/cpp/jni_wrapper.cc
new file mode 100644
index 000000000..61c812b30
--- /dev/null
+++ b/java/driver/jni/src/main/cpp/jni_wrapper.cc
@@ -0,0 +1,373 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include "org_apache_arrow_adbc_driver_jni_impl_NativeAdbc.h"
+
+#include <cassert>
+#include <cstring>
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+
+#include <arrow-adbc/adbc.h>
+#include <arrow-adbc/adbc_driver_manager.h>
+#include <jni.h>
+
+// We will use exceptions for error handling as that's easier with the JNI
+// model.
+
+namespace {
+
+/// Internal exception. Meant to be used with RaiseAdbcException and
+/// CHECK_ADBC_ERROR.
+struct AdbcException {
+ AdbcStatusCode code;
+ std::string message;
+
+ void ThrowJavaException(JNIEnv* env) const {
+ jclass exception_klass =
env->FindClass("org/apache/arrow/adbc/core/AdbcException");
+ assert(exception_klass != nullptr);
+ jmethodID exception_ctor =
+ env->GetMethodID(exception_klass, "<init>",
+ "(Ljava/lang/String;Ljava/lang/Throwable;"
+ "Lorg/apache/arrow/adbc/core/AdbcStatusCode;"
+ "Ljava/lang/String;I)V");
+ assert(exception_ctor != nullptr);
+
+ jclass status_klass =
env->FindClass("org/apache/arrow/adbc/core/AdbcStatusCode");
+ assert(status_klass != nullptr);
+
+ jfieldID status_field;
+
+ const char* sig = "Lorg/apache/arrow/adbc/core/AdbcStatusCode;";
+#define CASE(name) \
+ case ADBC_STATUS_##name: \
+ status_field = env->GetStaticFieldID(status_klass, #name, sig); \
+ break;
+
+ switch (code) {
+ CASE(UNKNOWN);
+ CASE(NOT_IMPLEMENTED);
+ CASE(NOT_FOUND);
+ CASE(ALREADY_EXISTS);
+ CASE(INVALID_ARGUMENT);
+ CASE(INVALID_STATE);
+ CASE(INVALID_DATA);
+ CASE(INTEGRITY);
+ CASE(INTERNAL);
+ CASE(IO);
+ CASE(CANCELLED);
+ CASE(TIMEOUT);
+ CASE(UNAUTHENTICATED);
+ CASE(UNAUTHORIZED);
+ default:
+ // uh oh
+ status_field = env->GetStaticFieldID(status_klass, "INTERNAL", sig);
+ break;
+ }
+#undef CASE
+ jobject status_jni = env->GetStaticObjectField(status_klass, status_field);
+
+ jstring message_jni = env->NewStringUTF(message.c_str());
+ auto exc = static_cast<jthrowable>(env->NewObject(
+ exception_klass, exception_ctor, message_jni, /*cause=*/nullptr,
status_jni,
+ /*sqlState=*/nullptr, /*vendorCode=*/0));
+ env->Throw(exc);
+ }
+};
+
+/// Signal an error to Java.
+void RaiseAdbcException(AdbcStatusCode code, const AdbcError& error) {
+ assert(code != ADBC_STATUS_OK);
+ throw AdbcException{
+ .code = code,
+ .message = std::string(error.message),
+ };
+}
+
+/// Check the result of an ADBC call and raise an exception to Java if it
failed.
+#define CHECK_ADBC_ERROR(expr, error) \
+ do { \
+ AdbcStatusCode status = (expr); \
+ if (status != ADBC_STATUS_OK) { \
+ ::RaiseAdbcException(status, error); \
+ } \
+ } while (0)
+
+/// Require that a Java class exists or error.
+jclass RequireImplClass(JNIEnv* env, std::string_view name) {
+ static std::string kPrefix = "org/apache/arrow/adbc/driver/jni/impl/";
+ std::string full_name = kPrefix + std::string(name);
+ jclass klass = env->FindClass(full_name.c_str());
+ if (klass == nullptr) {
+ throw AdbcException{
+ .code = ADBC_STATUS_INTERNAL,
+ .message = "[JNI] Could not find class " + full_name,
+ };
+ }
+ return klass;
+}
+
+/// Require that a Java method exists or error.
+jmethodID RequireMethod(JNIEnv* env, jclass klass, std::string_view name,
+ std::string_view signature) {
+ jmethodID method = env->GetMethodID(klass, name.data(), signature.data());
+ if (method == nullptr) {
+ std::string message = "[JNI] Could not find method ";
+ message += name;
+ message += " with signature ";
+ message += signature;
+ throw AdbcException{
+ .code = ADBC_STATUS_INTERNAL,
+ .message = std::move(message),
+ };
+ }
+ return method;
+}
+
+struct JniStringView {
+ JNIEnv* env;
+ jstring jni_string;
+ const char* value;
+
+ explicit JniStringView(JNIEnv* env, jstring jni_string)
+ : env(env), jni_string(jni_string), value(nullptr) {
+ if (jni_string == nullptr) {
+ throw AdbcException{ADBC_STATUS_INTERNAL, "Java string was nullptr"};
+ }
+ value = env->GetStringUTFChars(jni_string, nullptr);
+ if (value == nullptr) {
+ throw AdbcException{ADBC_STATUS_INTERNAL,
+ "Java string was nullptr (could not get string
contents)"};
+ }
+ }
+
+ ~JniStringView() {
+ if (jni_string == nullptr) {
+ return;
+ }
+
+ env->ReleaseStringUTFChars(jni_string, value);
+ jni_string = nullptr;
+ }
+};
+
+std::string GetJniString(JNIEnv* env, jstring jni_string) {
+ JniStringView view(env, jni_string);
+ return std::string(view.value);
+}
+
+std::optional<std::string> MaybeGetJniString(JNIEnv* env, jstring jni_string) {
+ if (jni_string == nullptr) {
+ return std::nullopt;
+ }
+ JniStringView view(env, jni_string);
+ return std::string(view.value);
+}
+
+template <typename Callable>
+auto WithJniString(JNIEnv* env, jstring jni_string, Callable&& callable) {
+ JniStringView view(env, jni_string);
+ return callable(view.value);
+}
+
+} // namespace
+
+extern "C" {
+
+JNIEXPORT jobject JNICALL
+Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_openDatabase(
+ JNIEnv* env, [[maybe_unused]] jclass self, jint version, jobjectArray
parameters) {
+ try {
+ struct AdbcError error = ADBC_ERROR_INIT;
+ auto db = std::make_unique<struct AdbcDatabase>();
+ std::memset(db.get(), 0, sizeof(struct AdbcDatabase));
+
+ CHECK_ADBC_ERROR(AdbcDatabaseNew(db.get(), &error), error);
+
+ const jsize num_params = env->GetArrayLength(parameters);
+ if (num_params % 2 != 0) {
+ throw AdbcException{
+ .code = ADBC_STATUS_INVALID_ARGUMENT,
+ .message = "[JNI] Must provide even number of parameters",
+ };
+ }
+ for (jsize i = 0; i < num_params; i += 2) {
+ // N.B. assuming String because Java side is typed as String[]
+ auto key =
reinterpret_cast<jstring>(env->GetObjectArrayElement(parameters, i));
+ auto value =
+ reinterpret_cast<jstring>(env->GetObjectArrayElement(parameters, i +
1));
+
+ JniStringView key_str(env, key);
+ JniStringView value_str(env, value);
+ CHECK_ADBC_ERROR(
+ AdbcDatabaseSetOption(db.get(), key_str.value, value_str.value,
&error), error);
+ }
+
+ CHECK_ADBC_ERROR(AdbcDatabaseInit(db.get(), &error), error);
+
+ jclass nativeHandleKlass = RequireImplClass(env, "NativeDatabaseHandle");
+ jmethodID nativeHandleCtor = RequireMethod(env, nativeHandleKlass,
"<init>", "(J)V");
+ jobject object =
+ env->NewObject(nativeHandleKlass, nativeHandleCtor,
+
static_cast<jlong>(reinterpret_cast<uintptr_t>(db.get())));
+ // Don't release until after we've constructed the object
+ db.release();
+ return object;
+ } catch (const AdbcException& e) {
+ e.ThrowJavaException(env);
+ return nullptr;
+ }
+}
+
+JNIEXPORT void JNICALL
+Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_closeDatabase(
+ JNIEnv* env, [[maybe_unused]] jclass self, jlong handle) {
+ try {
+ struct AdbcError error = ADBC_ERROR_INIT;
+ auto* ptr = reinterpret_cast<struct
AdbcDatabase*>(static_cast<uintptr_t>(handle));
+ CHECK_ADBC_ERROR(AdbcDatabaseRelease(ptr, &error), error);
+ delete ptr;
+ } catch (const AdbcException& e) {
+ e.ThrowJavaException(env);
+ }
+}
+
+JNIEXPORT jobject JNICALL
+Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_openConnection(
+ JNIEnv* env, [[maybe_unused]] jclass self, jlong database_handle) {
+ try {
+ struct AdbcError error = ADBC_ERROR_INIT;
+ auto conn = std::make_unique<struct AdbcConnection>();
+ std::memset(conn.get(), 0, sizeof(struct AdbcConnection));
+
+ auto* db =
+ reinterpret_cast<struct
AdbcDatabase*>(static_cast<uintptr_t>(database_handle));
+
+ CHECK_ADBC_ERROR(AdbcConnectionNew(conn.get(), &error), error);
+ CHECK_ADBC_ERROR(AdbcConnectionInit(conn.get(), db, &error), error);
+
+ jclass native_handle_class = RequireImplClass(env,
"NativeConnectionHandle");
+ jmethodID native_handle_ctor =
+ RequireMethod(env, native_handle_class, "<init>", "(J)V");
+ jobject object =
+ env->NewObject(native_handle_class, native_handle_ctor,
+
static_cast<jlong>(reinterpret_cast<uintptr_t>(conn.get())));
+ // Don't release until after we've constructed the object
+ conn.release();
+ return object;
+ } catch (const AdbcException& e) {
+ e.ThrowJavaException(env);
+ return nullptr;
+ }
+}
+
+JNIEXPORT void JNICALL
+Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_closeConnection(
+ JNIEnv* env, [[maybe_unused]] jclass self, jlong handle) {
+ try {
+ struct AdbcError error = ADBC_ERROR_INIT;
+ auto* ptr = reinterpret_cast<struct
AdbcConnection*>(static_cast<uintptr_t>(handle));
+ CHECK_ADBC_ERROR(AdbcConnectionRelease(ptr, &error), error);
+ delete ptr;
+ } catch (const AdbcException& e) {
+ e.ThrowJavaException(env);
+ }
+}
+
+JNIEXPORT jobject JNICALL
+Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_openStatement(
+ JNIEnv* env, [[maybe_unused]] jclass self, jlong connection_handle) {
+ try {
+ struct AdbcError error = ADBC_ERROR_INIT;
+ auto stmt = std::make_unique<struct AdbcStatement>();
+ std::memset(stmt.get(), 0, sizeof(struct AdbcStatement));
+
+ auto* conn = reinterpret_cast<struct AdbcConnection*>(
+ static_cast<uintptr_t>(connection_handle));
+
+ CHECK_ADBC_ERROR(AdbcStatementNew(conn, stmt.get(), &error), error);
+
+ jclass native_handle_class = RequireImplClass(env,
"NativeStatementHandle");
+ jmethodID native_handle_ctor =
+ RequireMethod(env, native_handle_class, "<init>", "(J)V");
+ jobject object =
+ env->NewObject(native_handle_class, native_handle_ctor,
+
static_cast<jlong>(reinterpret_cast<uintptr_t>(stmt.get())));
+ // Don't release until after we've constructed the object
+ stmt.release();
+ return object;
+ } catch (const AdbcException& e) {
+ e.ThrowJavaException(env);
+ return nullptr;
+ }
+}
+
+JNIEXPORT void JNICALL
+Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_closeStatement(
+ JNIEnv* env, [[maybe_unused]] jclass self, jlong handle) {
+ try {
+ struct AdbcError error = ADBC_ERROR_INIT;
+ auto* ptr = reinterpret_cast<struct
AdbcStatement*>(static_cast<uintptr_t>(handle));
+ CHECK_ADBC_ERROR(AdbcStatementRelease(ptr, &error), error);
+ delete ptr;
+ } catch (const AdbcException& e) {
+ e.ThrowJavaException(env);
+ }
+}
+
+JNIEXPORT jobject JNICALL
+Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementExecuteQuery(
+ JNIEnv* env, [[maybe_unused]] jclass self, jlong handle) {
+ try {
+ struct AdbcError error = ADBC_ERROR_INIT;
+ auto* ptr = reinterpret_cast<struct
AdbcStatement*>(static_cast<uintptr_t>(handle));
+ auto out = std::make_unique<struct ArrowArrayStream>();
+ std::memset(out.get(), 0, sizeof(struct ArrowArrayStream));
+ int64_t rows_affected = 0;
+ CHECK_ADBC_ERROR(AdbcStatementExecuteQuery(ptr, out.get(), &rows_affected,
&error),
+ error);
+
+ jclass native_result_class = RequireImplClass(env, "NativeQueryResult");
+ jmethodID native_result_ctor =
+ RequireMethod(env, native_result_class, "<init>", "(JJ)V");
+ jobject object =
+ env->NewObject(native_result_class, native_result_ctor, rows_affected,
+
static_cast<jlong>(reinterpret_cast<uintptr_t>(out.get())));
+ // Don't release until after we've constructed the object
+ out.release();
+ return object;
+ } catch (const AdbcException& e) {
+ e.ThrowJavaException(env);
+ }
+ return nullptr;
+}
+
+JNIEXPORT void JNICALL
+Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementSetSqlQuery(
+ JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring query) {
+ try {
+ struct AdbcError error = ADBC_ERROR_INIT;
+ auto* ptr = reinterpret_cast<struct
AdbcStatement*>(static_cast<uintptr_t>(handle));
+ JniStringView query_str(env, query);
+ CHECK_ADBC_ERROR(AdbcStatementSetSqlQuery(ptr, query_str.value, &error),
error);
+ } catch (const AdbcException& e) {
+ e.ThrowJavaException(env);
+ }
+}
+}
diff --git
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniConnection.java
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniConnection.java
new file mode 100644
index 000000000..e16f8c861
--- /dev/null
+++
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniConnection.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.arrow.adbc.driver.jni;
+
+import org.apache.arrow.adbc.core.AdbcConnection;
+import org.apache.arrow.adbc.core.AdbcException;
+import org.apache.arrow.adbc.core.AdbcStatement;
+import org.apache.arrow.adbc.driver.jni.impl.JniLoader;
+import org.apache.arrow.adbc.driver.jni.impl.NativeConnectionHandle;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.vector.ipc.ArrowReader;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public class JniConnection implements AdbcConnection {
+ private final BufferAllocator allocator;
+ private final NativeConnectionHandle handle;
+
+ public JniConnection(BufferAllocator allocator, NativeConnectionHandle
handle) {
+ this.allocator = allocator;
+ this.handle = handle;
+ }
+
+ @Override
+ public AdbcStatement createStatement() throws AdbcException {
+ return new JniStatement(allocator,
JniLoader.INSTANCE.openStatement(handle));
+ }
+
+ @Override
+ public ArrowReader getInfo(int @Nullable [] infoCodes) throws AdbcException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void close() {
+ handle.close();
+ }
+}
diff --git
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniDatabase.java
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniDatabase.java
new file mode 100644
index 000000000..1eaea0c5a
--- /dev/null
+++
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniDatabase.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.arrow.adbc.driver.jni;
+
+import org.apache.arrow.adbc.core.AdbcConnection;
+import org.apache.arrow.adbc.core.AdbcDatabase;
+import org.apache.arrow.adbc.core.AdbcException;
+import org.apache.arrow.adbc.driver.jni.impl.JniLoader;
+import org.apache.arrow.adbc.driver.jni.impl.NativeDatabaseHandle;
+import org.apache.arrow.memory.BufferAllocator;
+
+public class JniDatabase implements AdbcDatabase {
+ private final BufferAllocator allocator;
+ private final NativeDatabaseHandle handle;
+
+ public JniDatabase(BufferAllocator allocator, NativeDatabaseHandle handle) {
+ this.allocator = allocator;
+ this.handle = handle;
+ }
+
+ @Override
+ public AdbcConnection connect() throws AdbcException {
+ return new JniConnection(allocator,
JniLoader.INSTANCE.openConnection(handle));
+ }
+
+ @Override
+ public void close() {
+ handle.close();
+ }
+}
diff --git
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniDriver.java
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniDriver.java
new file mode 100644
index 000000000..91586f3b1
--- /dev/null
+++
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniDriver.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.arrow.adbc.driver.jni;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.arrow.adbc.core.AdbcDatabase;
+import org.apache.arrow.adbc.core.AdbcDriver;
+import org.apache.arrow.adbc.core.AdbcException;
+import org.apache.arrow.adbc.core.TypedKey;
+import org.apache.arrow.adbc.driver.jni.impl.JniLoader;
+import org.apache.arrow.adbc.driver.jni.impl.NativeDatabaseHandle;
+import org.apache.arrow.memory.BufferAllocator;
+
+/** An ADBC driver wrapping Arrow Flight SQL. */
+public class JniDriver implements AdbcDriver {
+ public static final TypedKey<String> PARAM_DRIVER = new
TypedKey<>("jni.driver", String.class);
+
+ private final BufferAllocator allocator;
+
+ public JniDriver(BufferAllocator allocator) {
+ this.allocator = Objects.requireNonNull(allocator);
+ }
+
+ @Override
+ public AdbcDatabase open(Map<String, Object> parameters) throws
AdbcException {
+ String driverName = PARAM_DRIVER.get(parameters);
+ if (driverName == null) {
+ throw AdbcException.invalidArgument(
+ "[JNI] Must provide String " + PARAM_DRIVER + " parameter");
+ }
+
+ Map<String, String> nativeParameters = new HashMap<>();
+ nativeParameters.put("driver", driverName);
+
+ NativeDatabaseHandle handle =
JniLoader.INSTANCE.openDatabase(nativeParameters);
+ return new JniDatabase(allocator, handle);
+ }
+}
diff --git
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniDriverFactory.java
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniDriverFactory.java
new file mode 100644
index 000000000..b67a534fd
--- /dev/null
+++
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniDriverFactory.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.arrow.adbc.driver.jni;
+
+import org.apache.arrow.adbc.core.AdbcDriver;
+import org.apache.arrow.adbc.drivermanager.AdbcDriverFactory;
+import org.apache.arrow.memory.BufferAllocator;
+
+/** Constructs new JniDriver instances. */
+public class JniDriverFactory implements AdbcDriverFactory {
+ @Override
+ public AdbcDriver getDriver(BufferAllocator allocator) {
+ return new JniDriver(allocator);
+ }
+}
diff --git
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniStatement.java
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniStatement.java
new file mode 100644
index 000000000..62d973748
--- /dev/null
+++
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniStatement.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.arrow.adbc.driver.jni;
+
+import org.apache.arrow.adbc.core.AdbcException;
+import org.apache.arrow.adbc.core.AdbcStatement;
+import org.apache.arrow.adbc.driver.jni.impl.JniLoader;
+import org.apache.arrow.adbc.driver.jni.impl.NativeQueryResult;
+import org.apache.arrow.adbc.driver.jni.impl.NativeStatementHandle;
+import org.apache.arrow.c.ArrowArrayStream;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.vector.ipc.ArrowReader;
+
+public class JniStatement implements AdbcStatement {
+ private final BufferAllocator allocator;
+ private final NativeStatementHandle handle;
+
+ public JniStatement(BufferAllocator allocator, NativeStatementHandle handle)
{
+ this.allocator = allocator;
+ this.handle = handle;
+ }
+
+ @Override
+ public void setSqlQuery(String query) throws AdbcException {
+ JniLoader.INSTANCE.statementSetSqlQuery(handle, query);
+ }
+
+ @Override
+ public QueryResult executeQuery() throws AdbcException {
+ NativeQueryResult result =
JniLoader.INSTANCE.statementExecuteQuery(handle);
+ // TODO: need to handle result in such a way that we free it even if we
error here
+ ArrowReader reader;
+ try (final ArrowArrayStream cStream =
ArrowArrayStream.wrap(result.cDataStream())) {
+ reader = org.apache.arrow.c.Data.importArrayStream(allocator, cStream);
+ }
+ return new QueryResult(result.rowsAffected(), reader);
+ }
+
+ @Override
+ public UpdateResult executeUpdate() throws AdbcException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void prepare() throws AdbcException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void close() {
+ handle.close();
+ }
+}
diff --git
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/JniLoader.java
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/JniLoader.java
new file mode 100644
index 000000000..d53c33616
--- /dev/null
+++
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/JniLoader.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.arrow.adbc.driver.jni.impl;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.util.Locale;
+import java.util.Map;
+import org.apache.arrow.adbc.core.AdbcException;
+
+/** Singleton wrapper protecting access to JNI functions. */
+public enum JniLoader {
+ INSTANCE;
+
+ JniLoader() {
+ // The JAR may contain multiple binaries for different platforms, so load
the appropriate one.
+ final String libraryName = "adbc_driver_jni";
+ String libraryToLoad =
+ libraryName + "/" + getNormalizedArch() + "/" +
System.mapLibraryName(libraryName);
+
+ try {
+ InputStream is =
JniLoader.class.getClassLoader().getResourceAsStream(libraryToLoad);
+ if (is == null) {
+ throw new FileNotFoundException(
+ "No JNI library for current platform, missing from JAR: " +
libraryToLoad);
+ }
+ File temp =
+ File.createTempFile("adbc-jni-", ".tmp", new
File(System.getProperty("java.io.tmpdir")));
+ temp.deleteOnExit();
+
+ try (is) {
+ Files.copy(is, temp.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ }
+ Runtime.getRuntime().load(temp.getAbsolutePath());
+ } catch (IOException e) {
+ throw new IllegalStateException("Error loading native library " +
libraryToLoad, e);
+ }
+ }
+
+ private String getNormalizedArch() {
+ // Be consistent with our CMake config
+ String arch = System.getProperty("os.arch").toLowerCase(Locale.US);
+ switch (arch) {
+ case "amd64":
+ return "x86_64";
+ case "aarch64":
+ return "aarch_64";
+ default:
+ throw new RuntimeException("ADBC JNI driver not supported on
architecture " + arch);
+ }
+ }
+
+ public NativeDatabaseHandle openDatabase(Map<String, String> parameters)
throws AdbcException {
+ String[] nativeParameters = new String[parameters.size() * 2];
+ int index = 0;
+ for (Map.Entry<String, String> parameter : parameters.entrySet()) {
+ nativeParameters[index++] = parameter.getKey();
+ nativeParameters[index++] = parameter.getValue();
+ }
+ return NativeAdbc.openDatabase(1001000, nativeParameters);
+ }
+
+ public NativeConnectionHandle openConnection(NativeDatabaseHandle database)
throws AdbcException {
+ return NativeAdbc.openConnection(database.getDatabaseHandle());
+ }
+
+ public NativeStatementHandle openStatement(NativeConnectionHandle connection)
+ throws AdbcException {
+ return NativeAdbc.openStatement(connection.getConnectionHandle());
+ }
+
+ public NativeQueryResult statementExecuteQuery(NativeStatementHandle
statement)
+ throws AdbcException {
+ return NativeAdbc.statementExecuteQuery(statement.getStatementHandle());
+ }
+
+ public void statementSetSqlQuery(NativeStatementHandle statement, String
query)
+ throws AdbcException {
+ NativeAdbc.statementSetSqlQuery(statement.getStatementHandle(), query);
+ }
+}
diff --git
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeAdbc.java
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeAdbc.java
new file mode 100644
index 000000000..f087c5fb2
--- /dev/null
+++
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeAdbc.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.arrow.adbc.driver.jni.impl;
+
+import org.apache.arrow.adbc.core.AdbcException;
+
+/** All the JNI methods. Don't use this directly, prefer {@link JniLoader}. */
+class NativeAdbc {
+ static native NativeDatabaseHandle openDatabase(int version, String[]
parameters)
+ throws AdbcException;
+
+ static native void closeDatabase(long handle) throws AdbcException;
+
+ static native NativeConnectionHandle openConnection(long databaseHandle)
throws AdbcException;
+
+ static native void closeConnection(long handle) throws AdbcException;
+
+ static native NativeStatementHandle openStatement(long connectionHandle)
throws AdbcException;
+
+ static native void closeStatement(long handle) throws AdbcException;
+
+ static native NativeQueryResult statementExecuteQuery(long handle) throws
AdbcException;
+
+ static native void statementSetSqlQuery(long handle, String query) throws
AdbcException;
+}
diff --git
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeConnectionHandle.java
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeConnectionHandle.java
new file mode 100644
index 000000000..7ad3efec8
--- /dev/null
+++
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeConnectionHandle.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.arrow.adbc.driver.jni.impl;
+
+public class NativeConnectionHandle extends NativeHandle {
+ NativeConnectionHandle(long nativeHandle) {
+ super(nativeHandle);
+ }
+
+ long getConnectionHandle() {
+ return state.nativeHandle;
+ }
+
+ @Override
+ Closer getCloseFunction() {
+ return NativeAdbc::closeConnection;
+ }
+}
diff --git
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeDatabaseHandle.java
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeDatabaseHandle.java
new file mode 100644
index 000000000..45c7aa376
--- /dev/null
+++
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeDatabaseHandle.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.arrow.adbc.driver.jni.impl;
+
+public class NativeDatabaseHandle extends NativeHandle {
+ NativeDatabaseHandle(long nativeHandle) {
+ super(nativeHandle);
+ }
+
+ long getDatabaseHandle() {
+ return state.nativeHandle;
+ }
+
+ @Override
+ Closer getCloseFunction() {
+ return NativeAdbc::closeDatabase;
+ }
+}
diff --git
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeHandle.java
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeHandle.java
new file mode 100644
index 000000000..a2975faad
--- /dev/null
+++
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeHandle.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.arrow.adbc.driver.jni.impl;
+
+import java.lang.ref.Cleaner;
+import org.apache.arrow.adbc.core.AdbcException;
+
+/** A wrapper around a C-allocated ADBC resource. */
+abstract class NativeHandle implements AutoCloseable {
+ static final Cleaner cleaner = Cleaner.create();
+
+ protected final State state;
+ private final Cleaner.Cleanable cleanable;
+
+ NativeHandle(long nativeHandle) {
+ this.state = new State(nativeHandle, getCloseFunction());
+ this.cleanable = cleaner.register(this, state);
+ }
+
+ /** Get the native function used to free the resource. */
+ abstract Closer getCloseFunction();
+
+ @Override
+ public void close() {
+ cleanable.clean();
+ }
+
+ protected static class State implements Runnable {
+ long nativeHandle;
+ private final Closer closer;
+
+ State(long nativeHandle, Closer closer) {
+ this.nativeHandle = nativeHandle;
+ this.closer = closer;
+ }
+
+ @Override
+ public void run() {
+ if (nativeHandle == 0) return;
+ final long handle = nativeHandle;
+ nativeHandle = 0;
+ try {
+ closer.close(handle);
+ } catch (AdbcException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ @FunctionalInterface
+ interface Closer {
+ void close(long handle) throws AdbcException;
+ }
+}
diff --git
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeQueryResult.java
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeQueryResult.java
new file mode 100644
index 000000000..526d615c3
--- /dev/null
+++
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeQueryResult.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.arrow.adbc.driver.jni.impl;
+
+public class NativeQueryResult {
+ private final long rowsAffected;
+ private final long cDataStream;
+
+ public NativeQueryResult(long rowsAffected, long cDataStream) {
+ this.rowsAffected = rowsAffected;
+ this.cDataStream = cDataStream;
+ }
+
+ public long rowsAffected() {
+ return rowsAffected;
+ }
+
+ public long cDataStream() {
+ return cDataStream;
+ }
+}
diff --git
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeStatementHandle.java
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeStatementHandle.java
new file mode 100644
index 000000000..cbc2a563f
--- /dev/null
+++
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeStatementHandle.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.arrow.adbc.driver.jni.impl;
+
+public class NativeStatementHandle extends NativeHandle {
+ NativeStatementHandle(long nativeHandle) {
+ super(nativeHandle);
+ }
+
+ long getStatementHandle() {
+ return state.nativeHandle;
+ }
+
+ @Override
+ Closer getCloseFunction() {
+ return NativeAdbc::closeStatement;
+ }
+}
diff --git
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/package-info.java
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/package-info.java
new file mode 100644
index 000000000..ed2ba096a
--- /dev/null
+++
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.arrow.adbc.driver.jni;
diff --git a/ci/conda_env_python.txt
b/java/driver/jni/src/main/resources/META-INF/services/org.apache.arrow.adbc.drivermanager.AdbcDriverFactory
similarity index 81%
copy from ci/conda_env_python.txt
copy to
java/driver/jni/src/main/resources/META-INF/services/org.apache.arrow.adbc.drivermanager.AdbcDriverFactory
index d0d4de475..c592768f1 100644
--- a/ci/conda_env_python.txt
+++
b/java/driver/jni/src/main/resources/META-INF/services/org.apache.arrow.adbc.drivermanager.AdbcDriverFactory
@@ -15,18 +15,4 @@
# specific language governing permissions and limitations
# under the License.
-Cython
-importlib-resources
-# nodejs is required by pyright
-nodejs >=13.0.0
-pandas
-pip
-pyarrow-all
-pyright
-pytest
-setuptools
-
-# For integration testing
-polars
-protobuf
-python-duckdb
+org.apache.arrow.adbc.driver.jni.NativeDriverFactory
diff --git
a/java/driver/jni/src/test/java/org/apache/arrow/adbc/driver/jni/JniDriverTest.java
b/java/driver/jni/src/test/java/org/apache/arrow/adbc/driver/jni/JniDriverTest.java
new file mode 100644
index 000000000..d72436bd0
--- /dev/null
+++
b/java/driver/jni/src/test/java/org/apache/arrow/adbc/driver/jni/JniDriverTest.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.arrow.adbc.driver.jni;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.LongStream;
+import org.apache.arrow.adbc.core.*;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.memory.RootAllocator;
+import org.apache.arrow.vector.BigIntVector;
+import org.apache.arrow.vector.VectorSchemaRoot;
+import org.assertj.core.api.InstanceOfAssertFactories;
+import org.junit.jupiter.api.Test;
+
+class JniDriverTest {
+ @Test
+ void minimal() throws Exception {
+ try (final BufferAllocator allocator = new RootAllocator()) {
+ JniDriver driver = new JniDriver(allocator);
+ Map<String, Object> parameters = new HashMap<>();
+ JniDriver.PARAM_DRIVER.set(parameters, "adbc_driver_sqlite");
+
+ driver.open(parameters).close();
+ }
+ }
+
+ @Test
+ void querySimple() throws Exception {
+ try (final BufferAllocator allocator = new RootAllocator()) {
+ JniDriver driver = new JniDriver(allocator);
+ Map<String, Object> parameters = new HashMap<>();
+ JniDriver.PARAM_DRIVER.set(parameters, "adbc_driver_sqlite");
+
+ try (final AdbcDatabase db = driver.open(parameters);
+ final AdbcConnection conn = db.connect();
+ final AdbcStatement stmt = conn.createStatement()) {
+ stmt.setSqlQuery("SELECT 1");
+ try (final AdbcStatement.QueryResult result = stmt.executeQuery()) {
+ assertThat(result.getReader().loadNextBatch()).isTrue();
+
assertThat(result.getReader().getVectorSchemaRoot().getVector(0).getObject(0))
+ .isEqualTo(1L);
+ }
+ }
+ }
+ }
+
+ @Test
+ void queryLarge() throws Exception {
+ try (final BufferAllocator allocator = new RootAllocator()) {
+ JniDriver driver = new JniDriver(allocator);
+ Map<String, Object> parameters = new HashMap<>();
+ JniDriver.PARAM_DRIVER.set(parameters, "adbc_driver_sqlite");
+
+ try (final AdbcDatabase db = driver.open(parameters);
+ final AdbcConnection conn = db.connect();
+ final AdbcStatement stmt = conn.createStatement()) {
+ stmt.setSqlQuery(
+ "WITH RECURSIVE seq(i) AS (SELECT 1 UNION ALL SELECT i + 1 FROM
seq WHERE i < 65536)"
+ + " SELECT * FROM seq");
+ try (final AdbcStatement.QueryResult result = stmt.executeQuery()) {
+ List<Long> seen = new ArrayList<>();
+ List<Long> expected = LongStream.range(1,
65537).boxed().collect(Collectors.toList());
+ while (result.getReader().loadNextBatch()) {
+ VectorSchemaRoot vsr = result.getReader().getVectorSchemaRoot();
+ //noinspection resource
+ BigIntVector i =
+ assertThat(vsr.getVector(0))
+
.asInstanceOf(InstanceOfAssertFactories.type(BigIntVector.class))
+ .actual();
+ for (int index = 0; index < vsr.getRowCount(); index++) {
+ assertThat(i.isNull(index)).isFalse();
+ seen.add(i.get(index));
+ }
+ }
+ assertThat(seen).isEqualTo(expected);
+ }
+ }
+ }
+ }
+
+ @Test
+ void queryError() throws Exception {
+ try (final BufferAllocator allocator = new RootAllocator()) {
+ JniDriver driver = new JniDriver(allocator);
+ Map<String, Object> parameters = new HashMap<>();
+ JniDriver.PARAM_DRIVER.set(parameters, "adbc_driver_sqlite");
+
+ try (final AdbcDatabase db = driver.open(parameters);
+ final AdbcConnection conn = db.connect();
+ final AdbcStatement stmt = conn.createStatement()) {
+ stmt.setSqlQuery("SELECT ?");
+ AdbcException exc =
+ assertThrows(
+ AdbcException.class,
+ () -> {
+ //noinspection EmptyTryBlock
+ try (final AdbcStatement.QueryResult result =
stmt.executeQuery()) {}
+ });
+ assertThat(exc.getStatus()).isEqualTo(AdbcStatusCode.INVALID_STATE);
+ assertThat(exc).hasMessageContaining("parameter count mismatch");
+ }
+ }
+ }
+}
diff --git a/java/pom.xml b/java/pom.xml
index 99311e689..8dd557163 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -179,6 +179,11 @@
<artifactId>spotless-maven-plugin</artifactId>
<version>2.44.3</version>
</plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo.natives</groupId>
+ <artifactId>maven-native-javah</artifactId>
+ <version>1.0-M1</version>
+ </plugin>
</plugins>
</pluginManagement>
@@ -201,6 +206,7 @@
<exclude>**/*.log</exclude>
<exclude>**/target/**</exclude>
<exclude>.mvn/jvm.config</exclude>
+ <exclude>build/</exclude>
</excludes>
</configuration>
<executions>
@@ -315,5 +321,12 @@
</plugins>
</build>
</profile>
+
+ <profile>
+ <id>jni</id>
+ <modules>
+ <module>driver/jni</module>
+ </modules>
+ </profile>
</profiles>
</project>