From 859f5e47008c87f4a860382314354750dc0f6829 Mon Sep 17 00:00:00 2001 From: David Li Date: Wed, 25 Dec 2024 22:28:36 -0500 Subject: [PATCH] feat(java/driver/jni): add JNI bindings to native driver manager Fixes #2027. --- c/driver_manager/adbc_driver_manager.cc | 2 +- java/CMakeLists.txt | 61 ++++++ java/driver/flight-sql/pom.xml | 3 +- java/driver/jni/CMakeLists.txt | 40 ++++ java/driver/jni/pom.xml | 128 +++++++++++ java/driver/jni/src/main/cpp/jni_wrapper.cc | 207 ++++++++++++++++++ .../arrow/adbc/driver/jni/JniConnection.java | 51 +++++ .../arrow/adbc/driver/jni/JniDatabase.java | 45 ++++ .../arrow/adbc/driver/jni/JniDriver.java | 54 +++++ .../adbc/driver/jni/JniDriverFactory.java | 30 +++ .../arrow/adbc/driver/jni/JniStatement.java | 50 +++++ .../arrow/adbc/driver/jni/impl/JniLoader.java | 66 ++++++ .../adbc/driver/jni/impl/NativeAdbc.java | 34 +++ .../driver/jni/impl/NativeAdbcException.java | 31 +++ .../adbc/driver/jni/impl/NativeHandle.java | 75 +++++++ .../driver/jni/impl/NativeHandleType.java | 25 +++ .../arrow/adbc/driver/jni/package-info.java | 18 ++ ...arrow.adbc.drivermanager.AdbcDriverFactory | 18 ++ .../arrow/adbc/driver/jni/JniDriverTest.java | 61 ++++++ java/pom.xml | 1 + 20 files changed, 997 insertions(+), 3 deletions(-) create mode 100644 java/CMakeLists.txt create mode 100644 java/driver/jni/CMakeLists.txt create mode 100644 java/driver/jni/pom.xml create mode 100644 java/driver/jni/src/main/cpp/jni_wrapper.cc create mode 100644 java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniConnection.java create mode 100644 java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniDatabase.java create mode 100644 java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniDriver.java create mode 100644 java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniDriverFactory.java create mode 100644 java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniStatement.java create mode 100644 java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/JniLoader.java create mode 100644 java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeAdbc.java create mode 100644 java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeAdbcException.java create mode 100644 java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeHandle.java create mode 100644 java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeHandleType.java create mode 100644 java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/package-info.java create mode 100644 java/driver/jni/src/main/resources/META-INF/services/org.apache.arrow.adbc.drivermanager.AdbcDriverFactory create mode 100644 java/driver/jni/src/test/java/org/apache/arrow/adbc/driver/jni/JniDriverTest.java 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/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..942303ce74 --- /dev/null +++ b/java/driver/jni/CMakeLists.txt @@ -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. + +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 + GENERATE_NATIVE_HEADERS + adbc_driver_jni_headers) + +add_library(adbc_driver_jni SHARED src/main/cpp/jni_wrapper.cc) +set_property(TARGET adbc_driver_jni PROPERTY OUTPUT_NAME "adbc_driver_jni") +target_link_libraries(adbc_driver_jni adbc_driver_jni_headers JNI::JNI + AdbcDriverManager::adbc_driver_manager_static) + +set(ARROW_JAVA_JNI_C_LIBDIR + "${CMAKE_INSTALL_PREFIX}/lib/adbc_driver_jni/${ADBC_ARCH_DIR}") +set(ADBC_DRIVER_JNI_C_BINDIR + "${CMAKE_INSTALL_PREFIX}/bin/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..f6892d112a --- /dev/null +++ b/java/driver/jni/pom.xml @@ -0,0 +1,128 @@ + + + + 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. + + + + com.github.ben-manes.caffeine + caffeine + + 2.9.3 + + + com.google.protobuf + protobuf-java + 4.29.2 + + + + + org.apache.arrow + arrow-memory-core + + + org.apache.arrow + arrow-vector + + + org.apache.arrow + flight-core + + + org.apache.arrow + flight-sql + + + + org.apache.arrow.adbc + adbc-core + + + org.apache.arrow.adbc + adbc-driver-manager + + + org.apache.arrow.adbc + adbc-sql + + + + + org.apache.arrow + flight-sql-jdbc-core + + + + + 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..d42c7d9bcf --- /dev/null +++ b/java/driver/jni/src/main/cpp/jni_wrapper.cc @@ -0,0 +1,207 @@ +// 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 databaseTypeId = + env->GetStaticFieldID(nativeHandleTypeKlass, "DATABASE", + "Lorg/apache/arrow/adbc/driver/jni/impl/NativeHandleType;"); + jobject databaseType = + env->GetStaticObjectField(nativeHandleTypeKlass, databaseTypeId); + + 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, databaseType, + 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); + } +} +} 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..96623a208b --- /dev/null +++ b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniConnection.java @@ -0,0 +1,51 @@ +/* + * 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.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 null; + } + + @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..1fa005533b --- /dev/null +++ b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/JniStatement.java @@ -0,0 +1,50 @@ +/* + * 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.NativeHandle; + +public class JniStatement implements AdbcStatement { + private final NativeHandle handle; + + public JniStatement(NativeHandle handle) { + this.handle = handle; + } + + @Override + public QueryResult executeQuery() throws AdbcException { + throw new UnsupportedOperationException(); + } + + @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..473e75447a --- /dev/null +++ b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/JniLoader.java @@ -0,0 +1,66 @@ +/* + * 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 com.google.common.base.Preconditions; +import java.util.Map; + +public enum JniLoader { + INSTANCE; + + JniLoader() { + // TODO: implement logic to unpack the library from resources as well + Runtime.getRuntime() + .load("/home/lidavidm/Code/arrow-adbc/java/build/driver/jni/libadbc_driver_jni.so"); + } + + 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); + } 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); + } 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..b559b3a983 --- /dev/null +++ b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeAdbc.java @@ -0,0 +1,34 @@ +/* + * 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(NativeHandle database) + throws NativeAdbcException; + + public static native void closeConnection(long handle) throws NativeAdbcException; + + public static native NativeHandle openStatement(NativeHandle database) throws NativeAdbcException; + + public static native void closeStatement(long handle) 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..a634023cd6 --- /dev/null +++ b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeHandle.java @@ -0,0 +1,75 @@ +/* + * 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(); + + // TODO: this could store an enum to identify the type of handle as additional checking + 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; + } + + 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/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..4c75729d7a --- /dev/null +++ b/java/driver/jni/src/test/java/org/apache/arrow/adbc/driver/jni/JniDriverTest.java @@ -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. + */ + +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.AdbcStatement; +import org.apache.arrow.memory.BufferAllocator; +import org.apache.arrow.memory.RootAllocator; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +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(); + } + } + } + } +} diff --git a/java/pom.xml b/java/pom.xml index 6b80751167..f4a26e8793 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -66,6 +66,7 @@ driver/jdbc-validation-derby driver/jdbc-validation-mssqlserver driver/jdbc-validation-postgresql + driver/jni driver/validation driver-manager sql