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;