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 7b38cf454 feat(java/driver/jni): metadata methods, validation suite 
(#3972)
7b38cf454 is described below

commit 7b38cf4543330592a40a5023b4e6b93f8f34d7ff
Author: Tornike Gurgenidze <[email protected]>
AuthorDate: Thu Feb 19 07:05:06 2026 +0400

    feat(java/driver/jni): metadata methods, validation suite (#3972)
    
    - adds bindings for: GetObjects, GetInfo, GetTableSchema, GetTableTypes
    - adds jni-validation-sqlite suite, most of the stuff in there is still
    disabled because of sqlite issues. I'll try later to add some of those
    to quirks and make it work.
    - fixes deferred bind bug that came up in validation suite. c driver
    expects user to rebind if data changes, while java seems to allow a
    caller to alter VectorSchemaRoot after it has been bound. Added
    `exportBind` method in JniStatement that exports bound data to c before
    each execution rather than during bind call.
---
 java/driver/jni-validation-sqlite/pom.xml          |  72 +++++++++
 .../jni/JniSqliteConnectionMetadataTest.java       |  70 +++++++++
 .../adbc/driver/jni/JniSqliteConnectionTest.java}  |  22 +--
 .../arrow/adbc/driver/jni/JniSqliteQuirks.java}    |  29 ++--
 .../adbc/driver/jni/JniSqliteStatementTest.java    |  61 ++++++++
 .../adbc/driver/jni/JniSqliteTransactionTest.java} |  24 ++-
 java/driver/jni/src/main/cpp/jni_wrapper.cc        | 162 +++++++++++++++++++--
 .../arrow/adbc/driver/jni/JniConnection.java       |  37 ++++-
 .../apache/arrow/adbc/driver/jni/JniStatement.java |  26 ++--
 .../arrow/adbc/driver/jni/impl/JniLoader.java      |  36 +++++
 .../arrow/adbc/driver/jni/impl/NativeAdbc.java     |  18 +++
 .../adbc/driver/jni/impl/NativeQueryResult.java    |  20 ++-
 .../adbc/driver/jni/impl/NativeSchemaResult.java   |  44 ++++++
 .../arrow/adbc/driver/jni/JniDriverTest.java       |  28 ++++
 java/pom.xml                                       |   6 +
 15 files changed, 584 insertions(+), 71 deletions(-)

diff --git a/java/driver/jni-validation-sqlite/pom.xml 
b/java/driver/jni-validation-sqlite/pom.xml
new file mode 100644
index 000000000..f50d8a111
--- /dev/null
+++ b/java/driver/jni-validation-sqlite/pom.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.arrow.adbc</groupId>
+    <artifactId>arrow-adbc-java-root</artifactId>
+    <version>0.23.0-SNAPSHOT</version>
+    <relativePath>../../pom.xml</relativePath>
+  </parent>
+
+  <artifactId>adbc-driver-jni-validation-sqlite</artifactId>
+  <packaging>jar</packaging>
+  <name>Arrow ADBC Driver JNI Validation with SQLite</name>
+  <description>Tests validating the JNI driver against SQLite.</description>
+
+  <properties>
+    <maven.deploy.skip>true</maven.deploy.skip>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.arrow.adbc</groupId>
+      <artifactId>adbc-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.arrow.adbc</groupId>
+      <artifactId>adbc-driver-jni</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <!-- Testing -->
+    <dependency>
+      <groupId>org.apache.arrow</groupId>
+      <artifactId>arrow-memory-netty</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.assertj</groupId>
+      <artifactId>assertj-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.arrow.adbc</groupId>
+      <artifactId>adbc-driver-validation</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git 
a/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteConnectionMetadataTest.java
 
b/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteConnectionMetadataTest.java
new file mode 100644
index 000000000..e805a1530
--- /dev/null
+++ 
b/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteConnectionMetadataTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.driver.testsuite.AbstractConnectionMetadataTest;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Disabled;
+
+public class JniSqliteConnectionMetadataTest extends 
AbstractConnectionMetadataTest {
+  @BeforeAll
+  static void beforeAll() {
+    quirks = new JniSqliteQuirks();
+  }
+
+  @Override
+  @Disabled("SQLite getInfo schema differs: map value type is List<Int32> 
instead of Int32")
+  public void getInfo() {}
+
+  @Override
+  @Disabled("SQLite getInfo schema differs: map value type is List<Int32> 
instead of Int32")
+  public void getInfoByCode() {}
+
+  @Override
+  @Disabled("SQLite does not support ALTER TABLE ... ALTER COLUMN ... SET NOT 
NULL")
+  public void getObjectsConstraints() {}
+
+  @Override
+  @Disabled(
+      "SQLite getObjects schema has nullable catalog_name/db_schema_name, test 
expects non-null")
+  public void getObjectsColumns() {}
+
+  @Override
+  @Disabled(
+      "SQLite getObjects schema has nullable catalog_name/db_schema_name, test 
expects non-null")
+  public void getObjectsCatalogs() {}
+
+  @Override
+  @Disabled(
+      "SQLite getObjects schema has nullable catalog_name/db_schema_name, test 
expects non-null")
+  public void getObjectsCatalogsPattern() {}
+
+  @Override
+  @Disabled(
+      "SQLite getObjects schema has nullable catalog_name/db_schema_name, test 
expects non-null")
+  public void getObjectsDbSchemas() {}
+
+  @Override
+  @Disabled(
+      "SQLite getObjects schema has nullable catalog_name/db_schema_name, test 
expects non-null")
+  public void getObjectsTables() {}
+
+  @Override
+  @Disabled("SQLite type affinity returns Int64 for all types, causing schema 
mismatch")
+  public void getTableSchema() {}
+}
diff --git 
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeQueryResult.java
 
b/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteConnectionTest.java
similarity index 66%
copy from 
java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeQueryResult.java
copy to 
java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteConnectionTest.java
index 526d615c3..e15182bca 100644
--- 
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeQueryResult.java
+++ 
b/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteConnectionTest.java
@@ -15,22 +15,14 @@
  * limitations under the License.
  */
 
-package org.apache.arrow.adbc.driver.jni.impl;
+package org.apache.arrow.adbc.driver.jni;
 
-public class NativeQueryResult {
-  private final long rowsAffected;
-  private final long cDataStream;
+import org.apache.arrow.adbc.driver.testsuite.AbstractConnectionTest;
+import org.junit.jupiter.api.BeforeAll;
 
-  public NativeQueryResult(long rowsAffected, long cDataStream) {
-    this.rowsAffected = rowsAffected;
-    this.cDataStream = cDataStream;
-  }
-
-  public long rowsAffected() {
-    return rowsAffected;
-  }
-
-  public long cDataStream() {
-    return cDataStream;
+public class JniSqliteConnectionTest extends AbstractConnectionTest {
+  @BeforeAll
+  static void beforeAll() {
+    quirks = new JniSqliteQuirks();
   }
 }
diff --git 
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeQueryResult.java
 
b/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteQuirks.java
similarity index 53%
copy from 
java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeQueryResult.java
copy to 
java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteQuirks.java
index 526d615c3..6d6124d43 100644
--- 
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeQueryResult.java
+++ 
b/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteQuirks.java
@@ -15,22 +15,25 @@
  * limitations under the License.
  */
 
-package org.apache.arrow.adbc.driver.jni.impl;
+package org.apache.arrow.adbc.driver.jni;
 
-public class NativeQueryResult {
-  private final long rowsAffected;
-  private final long cDataStream;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.arrow.adbc.core.AdbcDatabase;
+import org.apache.arrow.adbc.core.AdbcException;
+import org.apache.arrow.adbc.driver.testsuite.SqlValidationQuirks;
+import org.apache.arrow.memory.BufferAllocator;
 
-  public NativeQueryResult(long rowsAffected, long cDataStream) {
-    this.rowsAffected = rowsAffected;
-    this.cDataStream = cDataStream;
+public class JniSqliteQuirks extends SqlValidationQuirks {
+  @Override
+  public AdbcDatabase initDatabase(BufferAllocator allocator) throws 
AdbcException {
+    final Map<String, Object> parameters = new HashMap<>();
+    JniDriver.PARAM_DRIVER.set(parameters, "adbc_driver_sqlite");
+    return new JniDriver(allocator).open(parameters);
   }
 
-  public long rowsAffected() {
-    return rowsAffected;
-  }
-
-  public long cDataStream() {
-    return cDataStream;
+  @Override
+  public String defaultCatalog() {
+    return "main";
   }
 }
diff --git 
a/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteStatementTest.java
 
b/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteStatementTest.java
new file mode 100644
index 000000000..0f09213f8
--- /dev/null
+++ 
b/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteStatementTest.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.driver.testsuite.AbstractStatementTest;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Disabled;
+
+class JniSqliteStatementTest extends AbstractStatementTest {
+  @BeforeAll
+  static void beforeAll() {
+    quirks = new JniSqliteQuirks();
+  }
+
+  @Override
+  @Disabled("SQLite driver does not return expected ADBC status codes for 
schema mismatch")
+  public void bulkIngestAppendConflict() {}
+
+  @Override
+  @Disabled("SQLite driver returns INTERNAL instead of NOT_FOUND")
+  public void bulkIngestAppendNotFound() {}
+
+  @Override
+  @Disabled("SQLite driver returns INTERNAL instead of ALREADY_EXISTS")
+  public void bulkIngestCreateConflict() {}
+
+  @Override
+  @Disabled("Not yet implemented in JNI driver")
+  public void executeSchema() {}
+
+  @Override
+  @Disabled("Not yet implemented in JNI driver")
+  public void executeSchemaPrepared() {}
+
+  @Override
+  @Disabled("Not yet implemented in JNI driver")
+  public void executeSchemaParams() {}
+
+  @Override
+  @Disabled("Not yet implemented in JNI driver")
+  public void getParameterSchema() {}
+
+  @Override
+  @Disabled("SQLite promotes INT to BIGINT, causing strict schema equality 
check to fail")
+  public void prepareQueryWithParameters() {}
+}
diff --git 
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeQueryResult.java
 
b/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteTransactionTest.java
similarity index 66%
copy from 
java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeQueryResult.java
copy to 
java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteTransactionTest.java
index 526d615c3..18a9850c2 100644
--- 
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeQueryResult.java
+++ 
b/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteTransactionTest.java
@@ -15,22 +15,16 @@
  * limitations under the License.
  */
 
-package org.apache.arrow.adbc.driver.jni.impl;
+package org.apache.arrow.adbc.driver.jni;
 
-public class NativeQueryResult {
-  private final long rowsAffected;
-  private final long cDataStream;
+import org.apache.arrow.adbc.driver.testsuite.AbstractTransactionTest;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Disabled;
 
-  public NativeQueryResult(long rowsAffected, long cDataStream) {
-    this.rowsAffected = rowsAffected;
-    this.cDataStream = cDataStream;
-  }
-
-  public long rowsAffected() {
-    return rowsAffected;
-  }
-
-  public long cDataStream() {
-    return cDataStream;
+@Disabled("Transactions not yet implemented in JNI driver")
+public class JniSqliteTransactionTest extends AbstractTransactionTest {
+  @BeforeAll
+  static void beforeAll() {
+    quirks = new JniSqliteQuirks();
   }
 }
diff --git a/java/driver/jni/src/main/cpp/jni_wrapper.cc 
b/java/driver/jni/src/main/cpp/jni_wrapper.cc
index fdfea15b0..eb2564c5a 100644
--- a/java/driver/jni/src/main/cpp/jni_wrapper.cc
+++ b/java/driver/jni/src/main/cpp/jni_wrapper.cc
@@ -23,6 +23,7 @@
 #include <optional>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include <arrow-adbc/adbc.h>
 #include <arrow-adbc/adbc_driver_manager.h>
@@ -334,27 +335,34 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_closeStatement(
   }
 }
 
+jobject MakeNativeQueryResult(JNIEnv* env, jlong rows_affected,
+                              struct ArrowArrayStream* out) {
+  jclass native_result_class = RequireImplClass(env, "NativeQueryResult");
+  jmethodID native_result_ctor =
+      RequireMethod(env, native_result_class, "<init>", "(JJ)V");
+  return env->NewObject(native_result_class, native_result_ctor, rows_affected,
+                        static_cast<jlong>(reinterpret_cast<uintptr_t>(out)));
+}
+
+jobject MakeNativeSchemaResult(JNIEnv* env, struct ArrowSchema* schema) {
+  jclass native_result_class = RequireImplClass(env, "NativeSchemaResult");
+  jmethodID native_result_ctor =
+      RequireMethod(env, native_result_class, "<init>", "(J)V");
+  return env->NewObject(native_result_class, native_result_ctor,
+                        
static_cast<jlong>(reinterpret_cast<uintptr_t>(schema)));
+}
+
 JNIEXPORT jobject JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementExecuteQuery(
     JNIEnv* env, [[maybe_unused]] jclass self, jlong handle) {
   try {
     struct AdbcError error = ADBC_ERROR_INIT;
     auto* ptr = reinterpret_cast<struct 
AdbcStatement*>(static_cast<uintptr_t>(handle));
-    auto out = std::make_unique<struct ArrowArrayStream>();
-    std::memset(out.get(), 0, sizeof(struct ArrowArrayStream));
+    struct ArrowArrayStream out = {};
     int64_t rows_affected = 0;
-    CHECK_ADBC_ERROR(AdbcStatementExecuteQuery(ptr, out.get(), &rows_affected, 
&error),
-                     error);
+    CHECK_ADBC_ERROR(AdbcStatementExecuteQuery(ptr, &out, &rows_affected, 
&error), error);
 
-    jclass native_result_class = RequireImplClass(env, "NativeQueryResult");
-    jmethodID native_result_ctor =
-        RequireMethod(env, native_result_class, "<init>", "(JJ)V");
-    jobject object =
-        env->NewObject(native_result_class, native_result_ctor, rows_affected,
-                       
static_cast<jlong>(reinterpret_cast<uintptr_t>(out.get())));
-    // Don't release until after we've constructed the object
-    out.release();
-    return object;
+    return MakeNativeQueryResult(env, rows_affected, &out);
   } catch (const AdbcException& e) {
     e.ThrowJavaException(env);
   }
@@ -445,4 +453,132 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementSetOption(
     e.ThrowJavaException(env);
   }
 }
+
+JNIEXPORT jobject JNICALL
+Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetObjects(
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jint depth, 
jstring catalog,
+    jstring db_schema, jstring table_name, jobjectArray table_types,
+    jstring column_name) {
+  try {
+    struct AdbcError error = ADBC_ERROR_INIT;
+    auto* conn = reinterpret_cast<struct 
AdbcConnection*>(static_cast<uintptr_t>(handle));
+
+    // Nullable string parameters: null jstring → NULL for C API (meaning "no 
filter")
+    auto catalog_str = MaybeGetJniString(env, catalog);
+    auto db_schema_str = MaybeGetJniString(env, db_schema);
+    auto table_name_str = MaybeGetJniString(env, table_name);
+    auto column_name_str = MaybeGetJniString(env, column_name);
+
+    // Convert String[] table_types to const char** (NULL-terminated) or NULL
+    std::vector<std::string> table_type_strings;
+    std::vector<const char*> table_type_ptrs;
+    const char** c_table_types = nullptr;
+    if (table_types != nullptr) {
+      jsize len = env->GetArrayLength(table_types);
+      table_type_strings.reserve(len);
+      table_type_ptrs.reserve(len + 1);
+      for (jsize i = 0; i < len; i++) {
+        auto element =
+            reinterpret_cast<jstring>(env->GetObjectArrayElement(table_types, 
i));
+        table_type_strings.push_back(GetJniString(env, element));
+        table_type_ptrs.push_back(table_type_strings.back().c_str());
+      }
+      table_type_ptrs.push_back(nullptr);  // NULL terminator
+      c_table_types = table_type_ptrs.data();
+    }
+
+    struct ArrowArrayStream out = {};
+
+    CHECK_ADBC_ERROR(
+        AdbcConnectionGetObjects(
+            conn, static_cast<int>(depth), catalog_str ? catalog_str->c_str() 
: nullptr,
+            db_schema_str ? db_schema_str->c_str() : nullptr,
+            table_name_str ? table_name_str->c_str() : nullptr, c_table_types,
+            column_name_str ? column_name_str->c_str() : nullptr, &out, 
&error),
+        error);
+
+    return MakeNativeQueryResult(env, -1, &out);
+  } catch (const AdbcException& e) {
+    e.ThrowJavaException(env);
+  }
+  return nullptr;
+}
+
+JNIEXPORT jobject JNICALL
+Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetInfo(
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jintArray 
info_codes) {
+  try {
+    struct AdbcError error = ADBC_ERROR_INIT;
+    auto* conn = reinterpret_cast<struct 
AdbcConnection*>(static_cast<uintptr_t>(handle));
+
+    // Convert jintArray to uint32_t* + length (or NULL + 0 if array is null)
+    const uint32_t* c_info_codes = nullptr;
+    size_t info_codes_length = 0;
+    std::vector<uint32_t> info_codes_vec;
+    if (info_codes != nullptr) {
+      jsize len = env->GetArrayLength(info_codes);
+      info_codes_vec.resize(len);
+      env->GetIntArrayRegion(info_codes, 0, len,
+                             reinterpret_cast<jint*>(info_codes_vec.data()));
+      c_info_codes = info_codes_vec.data();
+      info_codes_length = static_cast<size_t>(len);
+    }
+
+    struct ArrowArrayStream out = {};
+
+    CHECK_ADBC_ERROR(
+        AdbcConnectionGetInfo(conn, c_info_codes, info_codes_length, &out, 
&error),
+        error);
+
+    return MakeNativeQueryResult(env, -1, &out);
+  } catch (const AdbcException& e) {
+    e.ThrowJavaException(env);
+  }
+  return nullptr;
+}
+
+JNIEXPORT jobject JNICALL
+Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetTableSchema(
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring catalog,
+    jstring db_schema, jstring table_name) {
+  try {
+    struct AdbcError error = ADBC_ERROR_INIT;
+    auto* conn = reinterpret_cast<struct 
AdbcConnection*>(static_cast<uintptr_t>(handle));
+
+    auto catalog_str = MaybeGetJniString(env, catalog);
+    auto db_schema_str = MaybeGetJniString(env, db_schema);
+    JniStringView table_name_str(env, table_name);
+
+    struct ArrowSchema schema = {};
+
+    CHECK_ADBC_ERROR(
+        AdbcConnectionGetTableSchema(conn, catalog_str ? catalog_str->c_str() 
: nullptr,
+                                     db_schema_str ? db_schema_str->c_str() : 
nullptr,
+                                     table_name_str.value, &schema, &error),
+        error);
+
+    return MakeNativeSchemaResult(env, &schema);
+  } catch (const AdbcException& e) {
+    e.ThrowJavaException(env);
+  }
+  return nullptr;
+}
+
+JNIEXPORT jobject JNICALL
+Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetTableTypes(
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle) {
+  try {
+    struct AdbcError error = ADBC_ERROR_INIT;
+    auto* conn = reinterpret_cast<struct 
AdbcConnection*>(static_cast<uintptr_t>(handle));
+
+    struct ArrowArrayStream out = {};
+
+    CHECK_ADBC_ERROR(AdbcConnectionGetTableTypes(conn, &out, &error), error);
+
+    return MakeNativeQueryResult(env, -1, &out);
+  } catch (const AdbcException& e) {
+    e.ThrowJavaException(env);
+  }
+  return nullptr;
+}
 }
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
index 7c1b9a4a6..ef214eea3 100644
--- 
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
@@ -26,6 +26,7 @@ import 
org.apache.arrow.adbc.driver.jni.impl.NativeConnectionHandle;
 import org.apache.arrow.adbc.driver.jni.impl.NativeStatementHandle;
 import org.apache.arrow.memory.BufferAllocator;
 import org.apache.arrow.vector.ipc.ArrowReader;
+import org.apache.arrow.vector.types.pojo.Schema;
 import org.checkerframework.checker.nullness.qual.Nullable;
 
 public class JniConnection implements AdbcConnection {
@@ -78,7 +79,41 @@ public class JniConnection implements AdbcConnection {
 
   @Override
   public ArrowReader getInfo(int @Nullable [] infoCodes) throws AdbcException {
-    throw new UnsupportedOperationException();
+    return JniLoader.INSTANCE.connectionGetInfo(handle, 
infoCodes).importStream(allocator);
+  }
+
+  @Override
+  public ArrowReader getObjects(
+      GetObjectsDepth depth,
+      String catalogPattern,
+      String dbSchemaPattern,
+      String tableNamePattern,
+      String[] tableTypes,
+      String columnNamePattern)
+      throws AdbcException {
+    return JniLoader.INSTANCE
+        .connectionGetObjects(
+            handle,
+            depth.ordinal(),
+            catalogPattern,
+            dbSchemaPattern,
+            tableNamePattern,
+            tableTypes,
+            columnNamePattern)
+        .importStream(allocator);
+  }
+
+  @Override
+  public Schema getTableSchema(String catalog, String dbSchema, String 
tableName)
+      throws AdbcException {
+    return JniLoader.INSTANCE
+        .connectionGetTableSchema(handle, catalog, dbSchema, tableName)
+        .importSchema(allocator);
+  }
+
+  @Override
+  public ArrowReader getTableTypes() throws AdbcException {
+    return 
JniLoader.INSTANCE.connectionGetTableTypes(handle).importStream(allocator);
   }
 
   @Override
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 b496049e1..5a68fb466 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
@@ -23,16 +23,15 @@ 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 {
   private final BufferAllocator allocator;
   private final NativeStatementHandle handle;
+  private VectorSchemaRoot bindRoot;
 
   public JniStatement(BufferAllocator allocator, NativeStatementHandle handle) 
{
     this.allocator = allocator;
@@ -46,11 +45,21 @@ public class JniStatement implements AdbcStatement {
 
   @Override
   public void bind(VectorSchemaRoot root) throws AdbcException {
+    this.bindRoot = root;
+  }
+
+  // The C Data export takes ownership of the data at bind time and ignores 
subsequent
+  // client changes to the bound root. Defer the export until execution so we 
capture
+  // the final state of the VectorSchemaRoot.
+  private void exportBind() throws AdbcException {
+    if (bindRoot == null) {
+      return;
+    }
     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);
+      Data.exportSchema(allocator, bindRoot.getSchema(), null, schema);
+      Data.exportVectorSchemaRoot(allocator, bindRoot, null, batch);
 
       JniLoader.INSTANCE.statementBind(handle, batch, schema);
     }
@@ -58,17 +67,14 @@ public class JniStatement implements AdbcStatement {
 
   @Override
   public QueryResult executeQuery() throws AdbcException {
+    exportBind();
     NativeQueryResult result = 
JniLoader.INSTANCE.statementExecuteQuery(handle);
-    // TODO: need to handle result in such a way that we free it even if we 
error here
-    ArrowReader reader;
-    try (final ArrowArrayStream cStream = 
ArrowArrayStream.wrap(result.cDataStream())) {
-      reader = org.apache.arrow.c.Data.importArrayStream(allocator, cStream);
-    }
-    return new QueryResult(result.rowsAffected(), reader);
+    return new QueryResult(result.rowsAffected(), 
result.importStream(allocator));
   }
 
   @Override
   public UpdateResult executeUpdate() throws AdbcException {
+    exportBind();
     long rowsAffected = JniLoader.INSTANCE.statementExecuteUpdate(handle);
     return new UpdateResult(rowsAffected);
   }
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 db8142867..a885e2ae2 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
@@ -118,4 +118,40 @@ public enum JniLoader {
       throws AdbcException {
     NativeAdbc.statementSetOption(statement.getStatementHandle(), key, value);
   }
+
+  public NativeQueryResult connectionGetObjects(
+      NativeConnectionHandle connection,
+      int depth,
+      String catalog,
+      String dbSchema,
+      String tableName,
+      String[] tableTypes,
+      String columnName)
+      throws AdbcException {
+    return NativeAdbc.connectionGetObjects(
+        connection.getConnectionHandle(),
+        depth,
+        catalog,
+        dbSchema,
+        tableName,
+        tableTypes,
+        columnName);
+  }
+
+  public NativeQueryResult connectionGetInfo(NativeConnectionHandle 
connection, int[] infoCodes)
+      throws AdbcException {
+    return NativeAdbc.connectionGetInfo(connection.getConnectionHandle(), 
infoCodes);
+  }
+
+  public NativeSchemaResult connectionGetTableSchema(
+      NativeConnectionHandle connection, String catalog, String dbSchema, 
String tableName)
+      throws AdbcException {
+    return NativeAdbc.connectionGetTableSchema(
+        connection.getConnectionHandle(), catalog, dbSchema, tableName);
+  }
+
+  public NativeQueryResult connectionGetTableTypes(NativeConnectionHandle 
connection)
+      throws AdbcException {
+    return 
NativeAdbc.connectionGetTableTypes(connection.getConnectionHandle());
+  }
 }
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 4e839c0b6..199662f15 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
@@ -51,4 +51,22 @@ class NativeAdbc {
   static native void statementPrepare(long handle) throws AdbcException;
 
   static native void statementSetOption(long handle, String key, String value) 
throws AdbcException;
+
+  static native NativeQueryResult connectionGetObjects(
+      long handle,
+      int depth,
+      String catalog,
+      String dbSchema,
+      String tableName,
+      String[] tableTypes,
+      String columnName)
+      throws AdbcException;
+
+  static native NativeQueryResult connectionGetInfo(long handle, int[] 
infoCodes)
+      throws AdbcException;
+
+  static native NativeSchemaResult connectionGetTableSchema(
+      long handle, String catalog, String dbSchema, String tableName) throws 
AdbcException;
+
+  static native NativeQueryResult connectionGetTableTypes(long handle) throws 
AdbcException;
 }
diff --git 
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeQueryResult.java
 
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeQueryResult.java
index 526d615c3..6399dfbb3 100644
--- 
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeQueryResult.java
+++ 
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeQueryResult.java
@@ -17,20 +17,32 @@
 
 package org.apache.arrow.adbc.driver.jni.impl;
 
+import org.apache.arrow.c.ArrowArrayStream;
+import org.apache.arrow.c.Data;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.vector.ipc.ArrowReader;
+
 public class NativeQueryResult {
   private final long rowsAffected;
-  private final long cDataStream;
+  private final ArrowArrayStream.Snapshot streamSnapshot;
 
   public NativeQueryResult(long rowsAffected, long cDataStream) {
     this.rowsAffected = rowsAffected;
-    this.cDataStream = cDataStream;
+    // Immediately snapshot the stream to take ownership of the contents.
+    // The address may point to a stack-allocated struct that becomes invalid
+    // after the JNI call returns.
+    this.streamSnapshot = ArrowArrayStream.wrap(cDataStream).snapshot();
   }
 
   public long rowsAffected() {
     return rowsAffected;
   }
 
-  public long cDataStream() {
-    return cDataStream;
+  /** Import the C Data stream into a Java ArrowReader. */
+  public ArrowReader importStream(BufferAllocator allocator) {
+    try (final ArrowArrayStream cStream = 
ArrowArrayStream.allocateNew(allocator)) {
+      cStream.save(streamSnapshot);
+      return Data.importArrayStream(allocator, cStream);
+    }
   }
 }
diff --git 
a/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeSchemaResult.java
 
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeSchemaResult.java
new file mode 100644
index 000000000..a09d3894b
--- /dev/null
+++ 
b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeSchemaResult.java
@@ -0,0 +1,44 @@
+/*
+ * 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 org.apache.arrow.c.ArrowSchema;
+import org.apache.arrow.c.CDataDictionaryProvider;
+import org.apache.arrow.c.Data;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.vector.types.pojo.Schema;
+
+public class NativeSchemaResult {
+  private final ArrowSchema.Snapshot schemaSnapshot;
+
+  public NativeSchemaResult(long cDataSchema) {
+    // Immediately snapshot the schema to take ownership of the contents.
+    // The address may point to a stack-allocated struct that becomes invalid
+    // after the JNI call returns.
+    this.schemaSnapshot = ArrowSchema.wrap(cDataSchema).snapshot();
+  }
+
+  /** Import the C Data schema into a Java Schema. */
+  public Schema importSchema(BufferAllocator allocator) {
+    try (final ArrowSchema cSchema = ArrowSchema.allocateNew(allocator);
+        final CDataDictionaryProvider provider = new 
CDataDictionaryProvider()) {
+      cSchema.save(schemaSnapshot);
+      return Data.importSchema(allocator, cSchema, provider);
+    }
+  }
+}
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 94fd9394e..a29f5de0b 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
@@ -38,6 +38,7 @@ 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.ipc.ArrowReader;
 import org.apache.arrow.vector.types.Types;
 import org.apache.arrow.vector.types.pojo.Field;
 import org.apache.arrow.vector.types.pojo.Schema;
@@ -272,6 +273,33 @@ class JniDriverTest {
     }
   }
 
+  @Test
+  void getObjects() throws Exception {
+    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()) {
+        // Create a table so there's something to find
+        try (final AdbcStatement stmt = conn.createStatement()) {
+          stmt.setSqlQuery("CREATE TABLE get_objects_test (id INTEGER, name 
TEXT)");
+          stmt.executeUpdate();
+        }
+
+        try (final ArrowReader reader =
+            conn.getObjects(AdbcConnection.GetObjectsDepth.ALL, null, null, 
null, null, null)) {
+          assertThat(reader.loadNextBatch()).isTrue();
+          VectorSchemaRoot root = reader.getVectorSchemaRoot();
+          assertThat(root.getRowCount()).isGreaterThan(0);
+          // First field should be catalog_name
+          
assertThat(root.getSchema().getFields().get(0).getName()).isEqualTo("catalog_name");
+        }
+      }
+    }
+  }
+
   @Test
   void bulkIngest() throws Exception {
     final Schema schema =
diff --git a/java/pom.xml b/java/pom.xml
index 14b222c94..d5f0fed79 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -138,6 +138,11 @@
         <artifactId>adbc-driver-validation</artifactId>
         <version>${project.version}</version>
       </dependency>
+      <dependency>
+        <groupId>org.apache.arrow.adbc</groupId>
+        <artifactId>adbc-driver-jni</artifactId>
+        <version>${project.version}</version>
+      </dependency>
       <dependency>
         <groupId>org.apache.arrow.adbc</groupId>
         <artifactId>adbc-driver-manager</artifactId>
@@ -328,6 +333,7 @@
       <id>jni</id>
       <modules>
         <module>driver/jni</module>
+        <module>driver/jni-validation-sqlite</module>
       </modules>
     </profile>
   </profiles>


Reply via email to