diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c9cbd0afb0..8fc5b909bf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -316,6 +316,40 @@ 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. + ### 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 0ce173a888..5e4a8b6dfe 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/docker-compose.yml b/docker-compose.yml index bf961cdb84..e662f81bde 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,8 +37,6 @@ services: build: context: . dockerfile: ci/docker/cpp-clang-latest.dockerfile - args: - VCPKG: ${VCPKG} volumes: - .:/adbc:delegated command: "bash -c 'export PATH=$PATH:/opt/go/bin CC=$(which clang) CXX=$(which clang++) && git config --global --add safe.directory /adbc && /adbc/ci/scripts/cpp_build.sh /adbc /adbc/build && env BUILD_ALL=0 BUILD_DRIVER_MANAGER=1 BUILD_DRIVER_SQLITE=1 /adbc/ci/scripts/cpp_test.sh /adbc/build'" @@ -56,7 +54,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/scripts/r_build.sh /adbc && /adbc/ci/scripts/docs_build.sh /adbc' - ############################ 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 0000000000..e4ae54ec38 --- /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 _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 63ad83b11f..d9e8935adb 100644 --- a/java/driver/flight-sql/pom.xml +++ b/java/driver/flight-sql/pom.xml @@ -35,8 +35,7 @@ com.github.ben-manes.caffeine caffeine - - 2.9.3 + 3.1.8 com.google.protobuf diff --git a/java/driver/jni/CMakeLists.txt b/java/driver/jni/CMakeLists.txt new file mode 100644 index 0000000000..28ea72f544 --- /dev/null +++ b/java/driver/jni/CMakeLists.txt @@ -0,0 +1,38 @@ +# 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_jar(adbc_driver_jni_jar + SOURCES src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeAdbc.java + src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeAdbcException.java + src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeHandle.java + src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeHandleType.java + src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeQueryResult.java + GENERATE_NATIVE_HEADERS + adbc_driver_jni_headers) + +add_library(adbc_driver_jni SHARED src/main/cpp/jni_wrapper.cc) +target_link_libraries(adbc_driver_jni adbc_driver_jni_headers 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/jni/pom.xml b/java/driver/jni/pom.xml new file mode 100644 index 0000000000..ef6628495a --- /dev/null +++ b/java/driver/jni/pom.xml @@ -0,0 +1,102 @@ + + + + 4.0.0 + + org.apache.arrow.adbc + arrow-adbc-java-root + 0.16.0-SNAPSHOT + ../../pom.xml + + + adbc-driver-jni + jar + Arrow ADBC Driver Native + An ADBC driver wrapping the native driver manager. + + + + + org.apache.arrow + arrow-c-data + + + org.apache.arrow + arrow-memory-core + + + org.apache.arrow + arrow-vector + + + + org.apache.arrow.adbc + adbc-core + + + org.apache.arrow.adbc + adbc-driver-manager + + + + + org.checkerframework + checker-qual + + + + + org.assertj + assertj-core + test + + + org.junit.jupiter + junit-jupiter + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.apache.arrow + flight-sql-jdbc-core + ${dep.arrow.version} + tests + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + ${project.basedir}/../../../testing/data + + + + + + 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 0000000000..22ae620f33 --- /dev/null +++ b/java/driver/jni/src/main/cpp/jni_wrapper.cc @@ -0,0 +1,343 @@ +// 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 +#include +#include +#include +#include + +#include +#include +#include + +// We will use exceptions for error handling as that's easier with the JNI +// model. + +namespace { + +struct AdbcException { + AdbcStatusCode code; + std::string message; + + void ThrowJavaException(JNIEnv* env) const { + jclass exception_klass = + env->FindClass("org/apache/arrow/adbc/driver/jni/impl/NativeAdbcException"); + assert(exception_klass != nullptr); + env->ThrowNew(exception_klass, message.c_str()); + } +}; + +void RaiseAdbcException(AdbcStatusCode code, const AdbcError& error) { + assert(code != ADBC_STATUS_OK); + throw AdbcException{ + .code = code, + .message = std::string(error.message), + }; +} + +#define CHECK_ADBC_ERROR(expr, error) \ + do { \ + AdbcStatusCode status = (expr); \ + if (status != ADBC_STATUS_OK) { \ + ::RaiseAdbcException(status, error); \ + } \ + } while (0) + +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; +} + +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) { + // TODO: + } + value = env->GetStringUTFChars(jni_string, nullptr); + if (value == nullptr) { + // TODO: + } + } + + ~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 MaybeGetJniString(JNIEnv* env, jstring jni_string) { + if (jni_string == nullptr) { + return std::nullopt; + } + JniStringView view(env, jni_string); + return std::string(view.value); +} + +template +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(); + 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(env->GetObjectArrayElement(parameters, i)); + auto value = + reinterpret_cast(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 nativeHandleTypeKlass = RequireImplClass(env, "NativeHandleType"); + jfieldID typeId = + env->GetStaticFieldID(nativeHandleTypeKlass, "DATABASE", + "Lorg/apache/arrow/adbc/driver/jni/impl/NativeHandleType;"); + jobject handleType = env->GetStaticObjectField(nativeHandleTypeKlass, typeId); + + jclass nativeHandleKlass = RequireImplClass(env, "NativeHandle"); + jmethodID nativeHandleCtor = + RequireMethod(env, nativeHandleKlass, "", + "(Lorg/apache/arrow/adbc/driver/jni/impl/NativeHandleType;J)V"); + jobject object = + env->NewObject(nativeHandleKlass, nativeHandleCtor, handleType, + static_cast(reinterpret_cast(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(static_cast(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(); + std::memset(conn.get(), 0, sizeof(struct AdbcConnection)); + + auto* db = + reinterpret_cast(static_cast(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, "NativeHandle"); + jclass native_handle_type_class = RequireImplClass(env, "NativeHandleType"); + jfieldID type_id = + env->GetStaticFieldID(native_handle_type_class, "CONNECTION", + "Lorg/apache/arrow/adbc/driver/jni/impl/NativeHandleType;"); + jobject handle_type = env->GetStaticObjectField(native_handle_type_class, type_id); + + jmethodID native_handle_ctor = + RequireMethod(env, native_handle_class, "", + "(Lorg/apache/arrow/adbc/driver/jni/impl/NativeHandleType;J)V"); + jobject object = + env->NewObject(native_handle_class, native_handle_ctor, handle_type, + static_cast(reinterpret_cast(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(static_cast(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(); + std::memset(stmt.get(), 0, sizeof(struct AdbcStatement)); + + auto* conn = reinterpret_cast( + static_cast(connection_handle)); + + CHECK_ADBC_ERROR(AdbcStatementNew(conn, stmt.get(), &error), error); + + jclass native_handle_class = RequireImplClass(env, "NativeHandle"); + jclass native_handle_type_class = RequireImplClass(env, "NativeHandleType"); + jfieldID type_id = + env->GetStaticFieldID(native_handle_type_class, "STATEMENT", + "Lorg/apache/arrow/adbc/driver/jni/impl/NativeHandleType;"); + jobject handle_type = env->GetStaticObjectField(native_handle_type_class, type_id); + + jmethodID native_handle_ctor = + RequireMethod(env, native_handle_class, "", + "(Lorg/apache/arrow/adbc/driver/jni/impl/NativeHandleType;J)V"); + jobject object = + env->NewObject(native_handle_class, native_handle_ctor, handle_type, + static_cast(reinterpret_cast(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(static_cast(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(static_cast(handle)); + auto out = std::make_unique(); + 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, "", "(JJ)V"); + jobject object = + env->NewObject(native_result_class, native_result_ctor, rows_affected, + static_cast(reinterpret_cast(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(static_cast(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 0000000000..307e3c3d94 --- /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.NativeHandle; +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 NativeHandle handle; + + public JniConnection(BufferAllocator allocator, NativeHandle 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 0000000000..9f847268d7 --- /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.NativeHandle; +import org.apache.arrow.memory.BufferAllocator; + +public class JniDatabase implements AdbcDatabase { + private final BufferAllocator allocator; + private final NativeHandle handle; + + public JniDatabase(BufferAllocator allocator, NativeHandle 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 0000000000..0873e4fddd --- /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.NativeHandle; +import org.apache.arrow.memory.BufferAllocator; + +/** An ADBC driver wrapping Arrow Flight SQL. */ +public class JniDriver implements AdbcDriver { + public static final TypedKey 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 parameters) throws AdbcException { + String driverName = PARAM_DRIVER.get(parameters); + if (driverName == null) { + throw AdbcException.invalidArgument( + "[Flight SQL] Must provide String " + PARAM_DRIVER + " parameter"); + } + + Map nativeParameters = new HashMap<>(); + nativeParameters.put("driver", driverName); + + NativeHandle 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 0000000000..b67a534fd5 --- /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 0000000000..a8e969bed2 --- /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.NativeHandle; +import org.apache.arrow.adbc.driver.jni.impl.NativeQueryResult; +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 NativeHandle handle; + + public JniStatement(BufferAllocator allocator, NativeHandle 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 0000000000..d130aec6eb --- /dev/null +++ b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/JniLoader.java @@ -0,0 +1,121 @@ +/* + * 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; +import org.apache.arrow.util.Preconditions; + +public enum JniLoader { + INSTANCE; + + JniLoader() { + 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(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: " + e); + } + } + + private String getNormalizedArch() { + 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 NativeHandle openDatabase(Map parameters) { + String[] nativeParameters = new String[parameters.size() * 2]; + int index = 0; + for (Map.Entry parameter : parameters.entrySet()) { + nativeParameters[index++] = parameter.getKey(); + nativeParameters[index++] = parameter.getValue(); + } + try { + return NativeAdbc.openDatabase(1001000, nativeParameters); + } catch (NativeAdbcException e) { + // TODO: convert to AdbcException + throw new RuntimeException(e); + } + } + + public NativeHandle openConnection(NativeHandle database) { + Preconditions.checkArgument(database.getHandleType() == NativeHandleType.DATABASE); + try { + return NativeAdbc.openConnection(database.getHandle()); + } catch (NativeAdbcException e) { + // TODO: convert to AdbcException + throw new RuntimeException(e); + } + } + + public NativeHandle openStatement(NativeHandle connection) { + Preconditions.checkArgument(connection.getHandleType() == NativeHandleType.CONNECTION); + try { + return NativeAdbc.openStatement(connection.getHandle()); + } catch (NativeAdbcException e) { + // TODO: convert to AdbcException + throw new RuntimeException(e); + } + } + + public NativeQueryResult statementExecuteQuery(NativeHandle statement) throws AdbcException { + try { + return NativeAdbc.statementExecuteQuery(statement.getHandle()); + } catch (NativeAdbcException e) { + // TODO: convert to AdbcException + throw new RuntimeException(e); + } + } + + public void statementSetSqlQuery(NativeHandle statement, String query) throws AdbcException { + try { + NativeAdbc.statementSetSqlQuery(statement.getHandle(), query); + } catch (NativeAdbcException e) { + // TODO: convert to AdbcException + throw new RuntimeException(e); + } + } +} 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 0000000000..b299f3b581 --- /dev/null +++ b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeAdbc.java @@ -0,0 +1,39 @@ +/* + * 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 NativeAdbc { + public static native NativeHandle openDatabase(int version, String[] parameters) + throws NativeAdbcException; + + public static native void closeDatabase(long handle) throws NativeAdbcException; + + public static native NativeHandle openConnection(long databaseHandle) throws NativeAdbcException; + + public static native void closeConnection(long handle) throws NativeAdbcException; + + public static native NativeHandle openStatement(long connectionHandle) throws NativeAdbcException; + + public static native void closeStatement(long handle) throws NativeAdbcException; + + public static native NativeQueryResult statementExecuteQuery(long handle) + throws NativeAdbcException; + + public static native void statementSetSqlQuery(long handle, String query) + throws NativeAdbcException; +} diff --git a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeAdbcException.java b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeAdbcException.java new file mode 100644 index 0000000000..ef205770d3 --- /dev/null +++ b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeAdbcException.java @@ -0,0 +1,31 @@ +/* + * 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; + +/** + * Wrapper for a {@code struct AdbcException} that doesn't depend on other Java code. + * + *

This breaks a dependency chain in compilation. If we directly used AdbcException, then we + * would need to compile the Java code before compiling JNI code, but we need the compiled JNI code + * to compile the Java code. + */ +public class NativeAdbcException extends Exception { + public NativeAdbcException(String message) { + super(message); + } +} 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 0000000000..49abbe8cd8 --- /dev/null +++ b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeHandle.java @@ -0,0 +1,78 @@ +/* + * 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; + +public class NativeHandle implements AutoCloseable { + static final Cleaner cleaner = Cleaner.create(); + + private final State state; + private final Cleaner.Cleanable cleanable; + + NativeHandle(NativeHandleType type, long nativeHandle) { + this.state = new State(type, nativeHandle); + this.cleanable = cleaner.register(this, state); + } + + @Override + public void close() { + cleanable.clean(); + } + + public NativeHandleType getHandleType() { + return state.type; + } + + long getHandle() { + return state.nativeHandle; + } + + private static class State implements Runnable { + private final NativeHandleType type; + long nativeHandle; + + State(NativeHandleType type, long nativeHandle) { + this.type = type; + this.nativeHandle = nativeHandle; + } + + @Override + public void run() { + if (nativeHandle == 0) return; + final long handle = nativeHandle; + nativeHandle = 0; + try { + switch (type) { + case DATABASE: + NativeAdbc.closeDatabase(handle); + break; + case CONNECTION: + NativeAdbc.closeConnection(handle); + break; + case STATEMENT: + NativeAdbc.closeStatement(handle); + break; + } + } catch (NativeAdbcException e) { + // TODO: convert to ADBC exception first + throw new RuntimeException(e); + } + } + } +} diff --git a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeHandleType.java b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeHandleType.java new file mode 100644 index 0000000000..dbf6ca63b1 --- /dev/null +++ b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeHandleType.java @@ -0,0 +1,25 @@ +/* + * 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 enum NativeHandleType { + DATABASE, + CONNECTION, + STATEMENT, + ; +} 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 0000000000..6ff7c69d70 --- /dev/null +++ b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeQueryResult.java @@ -0,0 +1,28 @@ +/* + * 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 { + public final long rowsAffected; + public final long cDataStream; + + public NativeQueryResult(long rowsAffected, long cDataStream) { + this.rowsAffected = rowsAffected; + this.cDataStream = cDataStream; + } +} 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 0000000000..ed2ba096a8 --- /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/java/driver/jni/src/main/resources/META-INF/services/org.apache.arrow.adbc.drivermanager.AdbcDriverFactory b/java/driver/jni/src/main/resources/META-INF/services/org.apache.arrow.adbc.drivermanager.AdbcDriverFactory new file mode 100644 index 0000000000..c592768f18 --- /dev/null +++ b/java/driver/jni/src/main/resources/META-INF/services/org.apache.arrow.adbc.drivermanager.AdbcDriverFactory @@ -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. + +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 0000000000..f400a304be --- /dev/null +++ b/java/driver/jni/src/test/java/org/apache/arrow/adbc/driver/jni/JniDriverTest.java @@ -0,0 +1,62 @@ +/* + * 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 java.util.HashMap; +import java.util.Map; +import org.apache.arrow.adbc.core.AdbcConnection; +import org.apache.arrow.adbc.core.AdbcDatabase; +import org.apache.arrow.adbc.core.AdbcStatement; +import org.apache.arrow.memory.BufferAllocator; +import org.apache.arrow.memory.RootAllocator; +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 parameters = new HashMap<>(); + JniDriver.PARAM_DRIVER.set(parameters, "adbc_driver_sqlite"); + + driver.open(parameters).close(); + } + } + + @Test + void query() throws Exception { + try (final BufferAllocator allocator = new RootAllocator()) { + JniDriver driver = new JniDriver(allocator); + Map 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); + } + } + } + } +} diff --git a/java/pom.xml b/java/pom.xml index 6b80751167..e5556c7c49 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -201,6 +201,7 @@ **/*.log **/target/** .mvn/jvm.config + build/ @@ -315,5 +316,12 @@ + + + jni + + driver/jni + +