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 05995bbe8 test(java/driver/jni): add more integration/unit tests
(#4440)
05995bbe8 is described below
commit 05995bbe87965729840691be82a4db5207ce4243
Author: David Li <[email protected]>
AuthorDate: Wed Jun 24 15:15:40 2026 -0700
test(java/driver/jni): add more integration/unit tests (#4440)
---
.../driver/jni/MultiDriverIntegrationTest.java | 145 +++++++++++++++++++++
java/driver/jni/CMakeLists.txt | 2 +-
java/driver/jni/src/main/cpp/jni_wrapper.cc | 25 ++++
.../arrow/adbc/driver/jni/impl/JniLoader.java | 5 +
.../arrow/adbc/driver/jni/impl/NativeAdbc.java | 3 +
.../arrow/adbc/driver/jni/impl/ImplTest.java | 122 +++++++++++++++++
6 files changed, 301 insertions(+), 1 deletion(-)
diff --git
a/java/driver/jni-validation/src/test/java/org/apache/arrow/adbc/driver/jni/MultiDriverIntegrationTest.java
b/java/driver/jni-validation/src/test/java/org/apache/arrow/adbc/driver/jni/MultiDriverIntegrationTest.java
new file mode 100644
index 000000000..7eba840a0
--- /dev/null
+++
b/java/driver/jni-validation/src/test/java/org/apache/arrow/adbc/driver/jni/MultiDriverIntegrationTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.assertj.core.api.Assertions.assertThatThrownBy;
+
+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.AdbcDriver;
+import org.apache.arrow.adbc.core.AdbcException;
+import org.apache.arrow.adbc.driver.testsuite.ArrowToJava;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.memory.RootAllocator;
+import org.apache.arrow.util.AutoCloseables;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assumptions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/** Integration test that uses multiple drivers simultaneously. */
+public class MultiDriverIntegrationTest {
+ BufferAllocator allocator;
+ JniDriver driver;
+
+ AdbcDatabase pgDb, mssqlDb, fsqlDb;
+ AdbcConnection pgConn, mssqlConn, fsqlConn;
+
+ @BeforeAll
+ static void beforeAll() {
+ Assumptions.assumeFalse(
+ FlightSqlSqliteIntegrationTest.URI == null ||
FlightSqlSqliteIntegrationTest.URI.isEmpty(),
+ String.format("Must set %s", FlightSqlSqliteIntegrationTest.URI_ENV));
+ Assumptions.assumeFalse(
+ PostgresIntegrationTest.URI == null ||
PostgresIntegrationTest.URI.isEmpty(),
+ String.format("Must set %s", PostgresIntegrationTest.URI_ENV));
+ Assumptions.assumeFalse(
+ SqlServerIntegrationTest.URI == null ||
SqlServerIntegrationTest.URI.isEmpty(),
+ String.format("Must set %s", SqlServerIntegrationTest.URI_ENV));
+ }
+
+ @BeforeEach
+ void beforeEach() throws Exception {
+ allocator = new RootAllocator();
+ driver = new JniDriver(allocator);
+
+ {
+ System.err.println(
+ "Connecting to Flight SQL with URI: " +
FlightSqlSqliteIntegrationTest.URI);
+ Map<String, Object> parameters = new HashMap<>();
+ JniDriver.PARAM_DRIVER.set(parameters, "adbc_driver_flightsql");
+ AdbcDriver.PARAM_URI.set(parameters, FlightSqlSqliteIntegrationTest.URI);
+ fsqlDb = driver.open(parameters);
+ fsqlConn = fsqlDb.connect();
+ }
+ {
+ System.err.println("Connecting to MSSQL with URI: " +
SqlServerIntegrationTest.URI);
+ Map<String, Object> parameters = new HashMap<>();
+ JniDriver.PARAM_DRIVER.set(parameters, "mssql");
+ AdbcDriver.PARAM_URI.set(parameters, SqlServerIntegrationTest.URI);
+ mssqlDb = driver.open(parameters);
+ mssqlConn = mssqlDb.connect();
+ }
+ {
+ System.err.println("Connecting to PostgreSQL with URI: " +
PostgresIntegrationTest.URI);
+ Map<String, Object> parameters = new HashMap<>();
+ JniDriver.PARAM_DRIVER.set(parameters, "adbc_driver_postgresql");
+ AdbcDriver.PARAM_URI.set(parameters, PostgresIntegrationTest.URI);
+ pgDb = driver.open(parameters);
+ pgConn = pgDb.connect();
+ }
+ }
+
+ @AfterEach
+ void afterEach() throws Exception {
+ AutoCloseables.close(pgConn, mssqlConn, fsqlConn, pgDb, mssqlDb, fsqlDb,
allocator);
+ }
+
+ @Test
+ void queryAll() throws Exception {
+ try (var pgStmt = pgConn.createStatement();
+ var mssqlStmt = mssqlConn.createStatement();
+ var fsqlStmt = fsqlConn.createStatement()) {
+ pgStmt.setSqlQuery("SELECT 1 as foo");
+ mssqlStmt.setSqlQuery("SELECT 1 as bar");
+ fsqlStmt.setSqlQuery("SELECT 1 as baz");
+
+ try (var pgRes = pgStmt.executeQuery();
+ var mssqlRes = mssqlStmt.executeQuery();
+ var fsqlRes = fsqlStmt.executeQuery()) {
+ assertThat(ArrowToJava.toIntegers(pgRes.getReader(),
"foo")).containsExactly(1);
+ assertThat(ArrowToJava.toIntegers(mssqlRes.getReader(),
"bar")).containsExactly(1);
+ assertThat(ArrowToJava.toLongs(fsqlRes.getReader(),
"baz")).containsExactly(1L);
+ }
+ }
+ }
+
+ @Test
+ void errorDriverDoesNotExist() {
+ Map<String, Object> parameters = new HashMap<>();
+ JniDriver.PARAM_DRIVER.set(parameters, "thisdriverdoesnotexist");
+ assertThatThrownBy(
+ () -> {
+ try (var db = driver.open(parameters)) {}
+ })
+ .isInstanceOf(AdbcException.class);
+ }
+
+ @Test
+ void errorFailedConnection() throws Exception {
+ Map<String, Object> parameters = new HashMap<>();
+ JniDriver.PARAM_DRIVER.set(parameters, "mssql");
+ AdbcDriver.PARAM_URI.set(parameters, "mssql://localhost:9999");
+ try (var db = driver.open(parameters)) {
+ assertThatThrownBy(db::connect).hasMessageContaining("Could not get
connection");
+ }
+ }
+
+ @Test
+ void errorBadConnectionParameter() throws Exception {
+ Map<String, Object> parameters = new HashMap<>();
+ JniDriver.PARAM_DRIVER.set(parameters, "mssql");
+ parameters.put("this parameter does not exist", "");
+ AdbcDriver.PARAM_URI.set(parameters, "mssql://localhost:9999");
+ assertThatThrownBy(() -> driver.open(parameters))
+ .hasMessageContaining("Unknown database option");
+ }
+}
diff --git a/java/driver/jni/CMakeLists.txt b/java/driver/jni/CMakeLists.txt
index b33d61879..45b9e2c52 100644
--- a/java/driver/jni/CMakeLists.txt
+++ b/java/driver/jni/CMakeLists.txt
@@ -23,7 +23,7 @@ add_custom_command(OUTPUT
${CMAKE_CURRENT_SOURCE_DIR}/target/headers/org_apache_
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
+ compile --also-make --projects :adbc-driver-jni
-Drat.skip=true
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeAdbc.java
)
diff --git a/java/driver/jni/src/main/cpp/jni_wrapper.cc
b/java/driver/jni/src/main/cpp/jni_wrapper.cc
index f39e3538b..2ebb833be 100644
--- a/java/driver/jni/src/main/cpp/jni_wrapper.cc
+++ b/java/driver/jni/src/main/cpp/jni_wrapper.cc
@@ -1562,4 +1562,29 @@
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_databaseSetOptionString(
e.ThrowJavaException(env);
}
}
+
+// wrapper around GetJniByteBuffer for direct unit testing
+JNIEXPORT jbyteArray JNICALL
+Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_internalGetByteBuffer(
+ JNIEnv* env, [[maybe_unused]] jclass self, jobject input) {
+ std::vector<uint8_t> scratch;
+ size_t length = 0;
+ try {
+ const uint8_t* raw = GetJniByteBuffer(env, input, scratch, length);
+ if (env->ExceptionCheck()) return nullptr;
+ // valid for raw to be nullptr if length == 0
+ jbyteArray result = env->NewByteArray(static_cast<jsize>(length));
+ if (result == nullptr || env->ExceptionCheck()) return nullptr;
+ if (length > 0) {
+ env->SetByteArrayRegion(result, 0, static_cast<jsize>(length),
+ reinterpret_cast<const jbyte*>(raw));
+ if (env->ExceptionCheck()) return nullptr;
+ }
+ return result;
+ } catch (const AdbcException& e) {
+ e.ThrowJavaException(env);
+ return nullptr;
+ }
+ return nullptr;
+}
}
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
index e2934741c..f4f258d96 100644
---
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
@@ -338,6 +338,11 @@ public enum JniLoader {
handle.getDatabaseHandle(), stringToUtf8(key), stringToUtf8(value));
}
+ /** For unit testing only. */
+ byte[] internalGetByteBuffer(ByteBuffer buf) throws AdbcException {
+ return NativeAdbc.internalGetByteBuffer(buf);
+ }
+
private static byte[] stringToUtf8(String value) {
return value == null ? null : value.getBytes(StandardCharsets.UTF_8);
}
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
index b23245430..2c022a4a5 100644
---
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
@@ -157,4 +157,7 @@ class NativeAdbc {
static native void databaseSetOptionString(long handle, byte[] key, byte[]
value)
throws AdbcException;
+
+ // Purely for unit testing.
+ static native byte[] internalGetByteBuffer(ByteBuffer input) throws
AdbcException;
}
diff --git
a/java/driver/jni/src/test/java/org/apache/arrow/adbc/driver/jni/impl/ImplTest.java
b/java/driver/jni/src/test/java/org/apache/arrow/adbc/driver/jni/impl/ImplTest.java
new file mode 100644
index 000000000..e102ebf22
--- /dev/null
+++
b/java/driver/jni/src/test/java/org/apache/arrow/adbc/driver/jni/impl/ImplTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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 static org.assertj.core.api.Assertions.assertThat;
+
+import java.nio.ByteBuffer;
+import org.junit.jupiter.api.Test;
+
+public class ImplTest {
+ @Test
+ void emptyHeap() throws Exception {
+ ByteBuffer buf = ByteBuffer.allocate(0);
+ assertThat(JniLoader.INSTANCE.internalGetByteBuffer(buf)).isEmpty();
+
+ buf = ByteBuffer.allocate(16);
+ buf.position(16);
+ assertThat(buf.remaining()).isEqualTo(0);
+ assertThat(JniLoader.INSTANCE.internalGetByteBuffer(buf)).isEmpty();
+ }
+
+ @Test
+ void emptyDirect() throws Exception {
+ ByteBuffer buf = ByteBuffer.allocateDirect(0);
+ assertThat(buf.isDirect()).isTrue();
+ assertThat(JniLoader.INSTANCE.internalGetByteBuffer(buf)).isEmpty();
+
+ buf = ByteBuffer.allocateDirect(16);
+ assertThat(buf.isDirect()).isTrue();
+ buf.position(16);
+ assertThat(buf.remaining()).isEqualTo(0);
+ assertThat(JniLoader.INSTANCE.internalGetByteBuffer(buf)).isEmpty();
+ }
+
+ @Test
+ void emptyArray() throws Exception {
+ ByteBuffer buf = ByteBuffer.wrap(new byte[0]);
+ assertThat(buf.hasArray()).isTrue();
+ assertThat(JniLoader.INSTANCE.internalGetByteBuffer(buf)).isEmpty();
+
+ buf = ByteBuffer.wrap(new byte[16]);
+ buf.position(16);
+ assertThat(buf.hasArray()).isTrue();
+ assertThat(buf.remaining()).isEqualTo(0);
+ assertThat(JniLoader.INSTANCE.internalGetByteBuffer(buf)).isEmpty();
+ }
+
+ @Test
+ void emptySlow() throws Exception {
+ ByteBuffer buf = ByteBuffer.wrap(new byte[0]).asReadOnlyBuffer();
+ // take the slow path
+ assertThat(buf.hasArray()).isFalse();
+ assertThat(buf.isDirect()).isFalse();
+ assertThat(JniLoader.INSTANCE.internalGetByteBuffer(buf)).isEmpty();
+
+ buf = ByteBuffer.wrap(new byte[16]);
+ buf.position(16);
+ buf = buf.asReadOnlyBuffer();
+ assertThat(buf.hasArray()).isFalse();
+ assertThat(buf.isDirect()).isFalse();
+ assertThat(buf.remaining()).isEqualTo(0);
+ assertThat(JniLoader.INSTANCE.internalGetByteBuffer(buf)).isEmpty();
+ }
+
+ @Test
+ void offsetHeap() throws Exception {
+ ByteBuffer buf = ByteBuffer.allocate(4);
+ buf.put((byte) 0);
+ buf.put((byte) 1);
+ buf.put((byte) 2);
+ buf.put((byte) 3);
+ buf.position(1);
+ assertThat(buf.remaining()).isEqualTo(3);
+ assertThat(JniLoader.INSTANCE.internalGetByteBuffer(buf)).isEqualTo(new
byte[] {1, 2, 3});
+ }
+
+ @Test
+ void offsetDirect() throws Exception {
+ ByteBuffer buf = ByteBuffer.allocateDirect(4);
+ buf.put((byte) 0);
+ buf.put((byte) 1);
+ buf.put((byte) 2);
+ buf.put((byte) 3);
+ buf.position(1);
+ assertThat(buf.remaining()).isEqualTo(3);
+ assertThat(JniLoader.INSTANCE.internalGetByteBuffer(buf)).isEqualTo(new
byte[] {1, 2, 3});
+ }
+
+ @Test
+ void offsetArray() throws Exception {
+ ByteBuffer buf = ByteBuffer.wrap(new byte[] {(byte) 0, (byte) 1, (byte) 2,
(byte) 3});
+ buf.position(1);
+ assertThat(buf.hasArray()).isTrue();
+ assertThat(buf.remaining()).isEqualTo(3);
+ assertThat(JniLoader.INSTANCE.internalGetByteBuffer(buf)).isEqualTo(new
byte[] {1, 2, 3});
+ }
+
+ @Test
+ void offsetSlow() throws Exception {
+ ByteBuffer buf = ByteBuffer.wrap(new byte[] {(byte) 0, (byte) 1, (byte) 2,
(byte) 3});
+ buf.position(1);
+ buf = buf.asReadOnlyBuffer();
+ assertThat(buf.hasArray()).isFalse();
+ assertThat(buf.isDirect()).isFalse();
+ assertThat(buf.remaining()).isEqualTo(3);
+ assertThat(JniLoader.INSTANCE.internalGetByteBuffer(buf)).isEqualTo(new
byte[] {1, 2, 3});
+ }
+}