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 b6ce56738 feat(java/driver/jni): implement getStatistics (#4361)
b6ce56738 is described below

commit b6ce56738771c6bf459f196311cf1f50faa95c22
Author: David Li <[email protected]>
AuthorDate: Fri Jun 5 14:40:23 2026 -0700

    feat(java/driver/jni): implement getStatistics (#4361)
    
    Closes #4360.
---
 .../org/apache/arrow/adbc/core/AdbcException.java  |  2 +-
 .../adbc/driver/jni/PostgresIntegrationTest.java   | 61 ++++++++++++++++++++++
 java/driver/jni/src/main/cpp/jni_wrapper.cc        | 39 ++++++++++++++
 .../arrow/adbc/driver/jni/JniConnection.java       | 15 ++++++
 .../arrow/adbc/driver/jni/impl/JniLoader.java      | 20 +++++++
 .../arrow/adbc/driver/jni/impl/NativeAdbc.java     | 10 ++++
 6 files changed, 146 insertions(+), 1 deletion(-)

diff --git 
a/java/core/src/main/java/org/apache/arrow/adbc/core/AdbcException.java 
b/java/core/src/main/java/org/apache/arrow/adbc/core/AdbcException.java
index 193bbaa96..104afe578 100644
--- a/java/core/src/main/java/org/apache/arrow/adbc/core/AdbcException.java
+++ b/java/core/src/main/java/org/apache/arrow/adbc/core/AdbcException.java
@@ -37,7 +37,7 @@ public class AdbcException extends Exception {
   private final AdbcStatusCode status;
   private final @Nullable String sqlState;
   private final int vendorCode;
-  private Collection<ErrorDetail> details;
+  private final Collection<ErrorDetail> details;
 
   public AdbcException(
       @Nullable String message,
diff --git 
a/java/driver/jni-validation/src/test/java/org/apache/arrow/adbc/driver/jni/PostgresIntegrationTest.java
 
b/java/driver/jni-validation/src/test/java/org/apache/arrow/adbc/driver/jni/PostgresIntegrationTest.java
index bfc715720..2cc9cef81 100644
--- 
a/java/driver/jni-validation/src/test/java/org/apache/arrow/adbc/driver/jni/PostgresIntegrationTest.java
+++ 
b/java/driver/jni-validation/src/test/java/org/apache/arrow/adbc/driver/jni/PostgresIntegrationTest.java
@@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
@@ -37,6 +38,7 @@ import org.apache.arrow.adbc.core.AdbcStatement;
 import org.apache.arrow.adbc.core.AdbcStatusCode;
 import org.apache.arrow.adbc.core.BulkIngestMode;
 import org.apache.arrow.adbc.core.IngestOption;
+import org.apache.arrow.adbc.core.StandardStatistics;
 import org.apache.arrow.adbc.core.TypedKey;
 import org.apache.arrow.adbc.driver.testsuite.ArrowToJava;
 import org.apache.arrow.memory.BufferAllocator;
@@ -45,6 +47,7 @@ import org.apache.arrow.vector.BigIntVector;
 import org.apache.arrow.vector.IntVector;
 import org.apache.arrow.vector.VarCharVector;
 import org.apache.arrow.vector.VectorSchemaRoot;
+import org.apache.arrow.vector.complex.ListVector;
 import org.apache.arrow.vector.ipc.ArrowReader;
 import org.apache.arrow.vector.types.Types;
 import org.apache.arrow.vector.types.pojo.Field;
@@ -201,6 +204,64 @@ class PostgresIntegrationTest {
     assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_IMPLEMENTED);
   }
 
+  @Test
+  void connectionStatisticNames() throws Exception {
+    try (final var reader = conn.getStatisticNames()) {
+      assertThat(reader.loadNextBatch()).isFalse();
+    }
+  }
+
+  @Test
+  void connectionStatistics() throws Exception {
+    try (final var stmt = conn.createStatement()) {
+      stmt.setSqlQuery("DROP TABLE IF EXISTS statstable");
+      stmt.executeUpdate();
+      stmt.setSqlQuery("CREATE TABLE statstable (a INT, b TEXT)");
+      stmt.executeUpdate();
+      stmt.setSqlQuery("INSERT INTO statstable VALUES (1, 'foo'), (2, 'spam 
and eggs'), (3, NULL)");
+      stmt.executeUpdate();
+      stmt.setSqlQuery("ANALYZE statstable");
+      stmt.executeUpdate();
+    }
+
+    try (final var reader = conn.getStatistics(null, "public", "statstable", 
true)) {
+      assertThat(reader.loadNextBatch()).isTrue();
+      var catalogDbSchemas = (ListVector) 
reader.getVectorSchemaRoot().getVector(1);
+      var schemas = catalogDbSchemas.getObject(0);
+      @SuppressWarnings("unchecked")
+      var schema = (Map<String, ?>) schemas.get(0);
+      @SuppressWarnings("unchecked")
+      var stats = (List<Map<String, ?>>) schema.get("db_schema_statistics");
+      var seen = new HashSet<Short>();
+      for (var stat : stats) {
+        assertThat(stat.get("table_name").toString()).isEqualTo("statstable");
+        short key = (Short) stat.get("statistic_key");
+        seen.add(key);
+        if (key == StandardStatistics.NULL_COUNT.getKey()) {
+          var columnName = stat.get("column_name").toString();
+          double statisticValue = (Double) stat.get("statistic_value");
+          if (columnName.equals("a")) {
+            assertThat(statisticValue).isEqualTo(0.0d);
+          } else if (columnName.equals("b")) {
+            assertThat(statisticValue).isGreaterThan(0.0d);
+          } else {
+            throw new AssertionError("Unexpected column name: " + columnName);
+          }
+        } else if (key == StandardStatistics.ROW_COUNT.getKey()) {
+          assertThat((Double) stat.get("statistic_value")).isGreaterThan(1.0d);
+        }
+        assertThat((Boolean) stat.get("statistic_is_approximate")).isTrue();
+      }
+      assertThat(reader.loadNextBatch()).isFalse();
+      assertThat(seen)
+          .contains(
+              StandardStatistics.AVERAGE_BYTE_WIDTH.getKey(),
+              StandardStatistics.DISTINCT_COUNT.getKey(),
+              StandardStatistics.NULL_COUNT.getKey(),
+              StandardStatistics.ROW_COUNT.getKey());
+    }
+  }
+
   @Test
   void bulkIngest() throws Exception {
     runSetup("DROP TABLE IF EXISTS foobar");
diff --git a/java/driver/jni/src/main/cpp/jni_wrapper.cc 
b/java/driver/jni/src/main/cpp/jni_wrapper.cc
index ef650fc08..d1f8309b3 100644
--- a/java/driver/jni/src/main/cpp/jni_wrapper.cc
+++ b/java/driver/jni/src/main/cpp/jni_wrapper.cc
@@ -1141,6 +1141,45 @@ 
Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionReadPartition(
   return nullptr;
 }
 
+JNIEXPORT jobject JNICALL
+Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetStatisticNames(
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle) {
+  struct AdbcError error = ADBC_ERROR_INIT;
+  auto* conn = reinterpret_cast<struct 
AdbcConnection*>(static_cast<uintptr_t>(handle));
+  struct ArrowArrayStream out = {};
+  try {
+    CHECK_ADBC_ERROR(AdbcConnectionGetStatisticNames(conn, &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_connectionGetStatistics(
+    JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring catalog,
+    jstring schema, jstring table, jboolean approximate) {
+  struct AdbcError error = ADBC_ERROR_INIT;
+  auto* conn = reinterpret_cast<struct 
AdbcConnection*>(static_cast<uintptr_t>(handle));
+  struct ArrowArrayStream out = {};
+  try {
+    std::optional<std::string> catalog_str = MaybeGetJniString(env, catalog);
+    std::optional<std::string> schema_str = MaybeGetJniString(env, schema);
+    std::optional<std::string> table_str = MaybeGetJniString(env, table);
+    CHECK_ADBC_ERROR(
+        AdbcConnectionGetStatistics(conn, catalog_str ? catalog_str->c_str() : 
nullptr,
+                                    schema_str ? schema_str->c_str() : nullptr,
+                                    table_str ? table_str->c_str() : nullptr, 
approximate,
+                                    &out, &error),
+        error);
+    return MakeNativeQueryResult(env, -1, &out);
+  } catch (const AdbcException& e) {
+    e.ThrowJavaException(env);
+  }
+  return nullptr;
+}
+
 JNIEXPORT jbyteArray JNICALL
 Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_databaseGetOptionBytes(
     JNIEnv* env, [[maybe_unused]] jclass self, jlong handle, jstring key) {
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 46203c874..a67b210f5 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
@@ -260,6 +260,21 @@ public class JniConnection implements AdbcConnection {
     return JniLoader.INSTANCE.connectionReadPartition(handle, 
descriptor).importStream(allocator);
   }
 
+  @Override
+  public ArrowReader getStatistics(
+      String catalogPattern, String dbSchemaPattern, String tableNamePattern, 
boolean approximate)
+      throws AdbcException {
+    return JniLoader.INSTANCE
+        .connectionGetStatistics(
+            handle, catalogPattern, dbSchemaPattern, tableNamePattern, 
approximate)
+        .importStream(allocator);
+  }
+
+  @Override
+  public ArrowReader getStatisticNames() throws AdbcException {
+    return 
JniLoader.INSTANCE.connectionGetStatisticNames(handle).importStream(allocator);
+  }
+
   @Override
   public void close() {
     handle.close();
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 b081b4b0f..9b8fbf916 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
@@ -258,6 +258,26 @@ public enum JniLoader {
     NativeAdbc.connectionSetOptionString(connection.getConnectionHandle(), 
key, value);
   }
 
+  public NativeQueryResult connectionGetStatistics(
+      NativeConnectionHandle connection,
+      String catalogPattern,
+      String dbSchemaPattern,
+      String tableNamePattern,
+      boolean approximate)
+      throws AdbcException {
+    return NativeAdbc.connectionGetStatistics(
+        connection.getConnectionHandle(),
+        catalogPattern,
+        dbSchemaPattern,
+        tableNamePattern,
+        approximate);
+  }
+
+  public NativeQueryResult connectionGetStatisticNames(NativeConnectionHandle 
connection)
+      throws AdbcException {
+    return 
NativeAdbc.connectionGetStatisticNames(connection.getConnectionHandle());
+  }
+
   public byte[] databaseGetOptionBytes(NativeDatabaseHandle handle, String key)
       throws AdbcException {
     return NativeAdbc.databaseGetOptionBytes(handle.getDatabaseHandle(), key);
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 68ad348a7..dff4cfde1 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
@@ -127,6 +127,16 @@ class NativeAdbc {
   static native void connectionSetOptionString(long handle, String key, String 
value)
       throws AdbcException;
 
+  static native NativeQueryResult connectionGetStatisticNames(long handle) 
throws AdbcException;
+
+  static native NativeQueryResult connectionGetStatistics(
+      long handle,
+      String catalogPattern,
+      String dbSchemaPattern,
+      String tableNamePattern,
+      boolean approximate)
+      throws AdbcException;
+
   static native byte[] databaseGetOptionBytes(long handle, String key) throws 
AdbcException;
 
   static native double databaseGetOptionDouble(long handle, String key) throws 
AdbcException;

Reply via email to