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