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});
+  }
+}

Reply via email to