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 37497315a feat(java/driver/jni): implement parameter binding (#3370)
37497315a is described below

commit 37497315ac78cc9e6cfa70eb8f9533f5dbc02322
Author: David Li <[email protected]>
AuthorDate: Mon Sep 1 12:46:13 2025 +0900

    feat(java/driver/jni): implement parameter binding (#3370)
    
    The Java API lacks a way to bind a stream, due to the general lack of a
    good interface for a stream in Java. So that isn't supported.
    
    Fixes #3369.
---
 java/driver/jni/src/main/cpp/jni_wrapper.cc        | 29 +++++++++++++++
 .../apache/arrow/adbc/driver/jni/JniStatement.java | 16 ++++++++
 .../arrow/adbc/driver/jni/impl/JniLoader.java      |  8 ++++
 .../arrow/adbc/driver/jni/impl/NativeAdbc.java     |  8 ++++
 .../arrow/adbc/driver/jni/JniDriverTest.java       | 43 +++++++++++++++++++++-
 5 files changed, 103 insertions(+), 1 deletion(-)

diff --git a/java/driver/jni/src/main/cpp/jni_wrapper.cc 
b/java/driver/jni/src/main/cpp/jni_wrapper.cc
index 61c812b30..2298a9924 100644
--- a/java/driver/jni/src/main/cpp/jni_wrapper.cc
+++ b/java/driver/jni/src/main/cpp/jni_wrapper.cc
@@ -370,4 +370,33 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementSetSqlQuery(
     e.ThrowJavaException(env);
   }
 }
+
+JNIEXPORT void JNICALL
+Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementBind(
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jlong values, 
jlong schema) {
+  try {
+    struct AdbcError error = ADBC_ERROR_INIT;
+    auto* ptr = reinterpret_cast<struct 
AdbcStatement*>(static_cast<uintptr_t>(handle));
+    auto* c_batch = reinterpret_cast<struct 
ArrowArray*>(static_cast<uintptr_t>(values));
+    auto* c_schema =
+        reinterpret_cast<struct ArrowSchema*>(static_cast<uintptr_t>(schema));
+    CHECK_ADBC_ERROR(AdbcStatementBind(ptr, c_batch, c_schema, &error), error);
+  } catch (const AdbcException& e) {
+    e.ThrowJavaException(env);
+  }
+}
+
+JNIEXPORT void JNICALL
+Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementBindStream(
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jlong stream) {
+  try {
+    struct AdbcError error = ADBC_ERROR_INIT;
+    auto* ptr = reinterpret_cast<struct 
AdbcStatement*>(static_cast<uintptr_t>(handle));
+    auto* c_stream =
+        reinterpret_cast<struct 
ArrowArrayStream*>(static_cast<uintptr_t>(stream));
+    CHECK_ADBC_ERROR(AdbcStatementBindStream(ptr, c_stream, &error), error);
+  } catch (const AdbcException& e) {
+    e.ThrowJavaException(env);
+  }
+}
 }
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
index 62d973748..9e69698d5 100644
--- 
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
@@ -22,8 +22,12 @@ import org.apache.arrow.adbc.core.AdbcStatement;
 import org.apache.arrow.adbc.driver.jni.impl.JniLoader;
 import org.apache.arrow.adbc.driver.jni.impl.NativeQueryResult;
 import org.apache.arrow.adbc.driver.jni.impl.NativeStatementHandle;
+import org.apache.arrow.c.ArrowArray;
 import org.apache.arrow.c.ArrowArrayStream;
+import org.apache.arrow.c.ArrowSchema;
+import org.apache.arrow.c.Data;
 import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.vector.VectorSchemaRoot;
 import org.apache.arrow.vector.ipc.ArrowReader;
 
 public class JniStatement implements AdbcStatement {
@@ -40,6 +44,18 @@ public class JniStatement implements AdbcStatement {
     JniLoader.INSTANCE.statementSetSqlQuery(handle, query);
   }
 
+  @Override
+  public void bind(VectorSchemaRoot root) throws AdbcException {
+    try (final ArrowArray batch = ArrowArray.allocateNew(allocator);
+        final ArrowSchema schema = ArrowSchema.allocateNew(allocator)) {
+      // TODO(lidavidm): we may need a way to separately provide a dictionary 
provider
+      Data.exportSchema(allocator, root.getSchema(), null, schema);
+      Data.exportVectorSchemaRoot(allocator, root, null, batch);
+
+      JniLoader.INSTANCE.statementBind(handle, batch, schema);
+    }
+  }
+
   @Override
   public QueryResult executeQuery() throws AdbcException {
     NativeQueryResult result = 
JniLoader.INSTANCE.statementExecuteQuery(handle);
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 d53c33616..9180c37fd 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
@@ -26,6 +26,8 @@ import java.nio.file.StandardCopyOption;
 import java.util.Locale;
 import java.util.Map;
 import org.apache.arrow.adbc.core.AdbcException;
+import org.apache.arrow.c.ArrowArray;
+import org.apache.arrow.c.ArrowSchema;
 
 /** Singleton wrapper protecting access to JNI functions. */
 public enum JniLoader {
@@ -97,4 +99,10 @@ public enum JniLoader {
       throws AdbcException {
     NativeAdbc.statementSetSqlQuery(statement.getStatementHandle(), query);
   }
+
+  public void statementBind(NativeStatementHandle statement, ArrowArray batch, 
ArrowSchema schema)
+      throws AdbcException {
+    NativeAdbc.statementBind(
+        statement.getStatementHandle(), batch.memoryAddress(), 
schema.memoryAddress());
+  }
 }
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 f087c5fb2..e9f652e60 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
@@ -37,4 +37,12 @@ class NativeAdbc {
   static native NativeQueryResult statementExecuteQuery(long handle) throws 
AdbcException;
 
   static native void statementSetSqlQuery(long handle, String query) throws 
AdbcException;
+
+  static native void statementBind(long handle, long values, long schema) 
throws AdbcException;
+
+  // TODO(lidavidm): we need a way to bind an ArrowReader (or some other 
suitable interface that
+  // doesn't exist in arrow-java; see the discussion around the Avro reader 
about how ArrowReader
+  // isn't a very general interface)
+  @SuppressWarnings("unused")
+  static native void statementBindStream(long handle, long stream) throws 
AdbcException;
 }
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
index ae913a1aa..98e844b10 100644
--- 
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
@@ -22,16 +22,24 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
 import java.util.stream.LongStream;
-import org.apache.arrow.adbc.core.*;
+import org.apache.arrow.adbc.core.AdbcConnection;
+import org.apache.arrow.adbc.core.AdbcDatabase;
+import org.apache.arrow.adbc.core.AdbcException;
+import org.apache.arrow.adbc.core.AdbcStatement;
+import org.apache.arrow.adbc.core.AdbcStatusCode;
 import org.apache.arrow.memory.BufferAllocator;
 import org.apache.arrow.memory.RootAllocator;
 import org.apache.arrow.vector.BigIntVector;
 import org.apache.arrow.vector.VectorSchemaRoot;
+import org.apache.arrow.vector.types.Types;
+import org.apache.arrow.vector.types.pojo.Field;
+import org.apache.arrow.vector.types.pojo.Schema;
 import org.assertj.core.api.InstanceOfAssertFactories;
 import org.junit.jupiter.api.Test;
 
@@ -136,6 +144,7 @@ class JniDriverTest {
       JniDriver driver = new JniDriver(allocator);
       Map<String, Object> parameters = new HashMap<>();
       JniDriver.PARAM_DRIVER.set(parameters, "adbc_driver_sqlite");
+
       parameters.put("uri", "file:" + tmpPath);
 
       try (final AdbcDatabase db = driver.open(parameters);
@@ -159,6 +168,38 @@ class JniDriverTest {
         try (final AdbcStatement.QueryResult result = stmt.executeQuery()) {
           assertThat(result.getReader().loadNextBatch()).isTrue();
           
assertThat(result.getReader().getVectorSchemaRoot().getRowCount()).isEqualTo(3);
+
+          assertThat(result.getReader().loadNextBatch()).isFalse();
+        }
+      }
+    }
+  }
+
+  void queryParams() throws Exception {
+    final Schema paramSchema =
+        new Schema(Collections.singletonList(Field.nullable("", 
Types.MinorType.BIGINT.getType())));
+    try (final BufferAllocator allocator = new RootAllocator()) {
+      JniDriver driver = new JniDriver(allocator);
+      Map<String, Object> parameters = new HashMap<>();
+      JniDriver.PARAM_DRIVER.set(parameters, "adbc_driver_sqlite");
+
+      try (final AdbcDatabase db = driver.open(parameters);
+          final AdbcConnection conn = db.connect();
+          final AdbcStatement stmt = conn.createStatement();
+          final VectorSchemaRoot root = VectorSchemaRoot.create(paramSchema, 
allocator)) {
+        ((BigIntVector) root.getVector(0)).setSafe(0, 41);
+        ((BigIntVector) root.getVector(0)).setNull(1);
+        root.setRowCount(2);
+
+        stmt.setSqlQuery("SELECT 1 + ?");
+        stmt.bind(root);
+
+        try (final AdbcStatement.QueryResult result = stmt.executeQuery()) {
+          assertThat(result.getReader().loadNextBatch()).isTrue();
+          
assertThat(result.getReader().getVectorSchemaRoot().getVector(0).getObject(0))
+              .isEqualTo(42L);
+          
assertThat(result.getReader().getVectorSchemaRoot().getVector(0).getObject(1)).isNull();
+
           assertThat(result.getReader().loadNextBatch()).isFalse();
         }
       }

Reply via email to