From 14f108100e78627b54d90f3ad2231b7b6378a6ad Mon Sep 17 00:00:00 2001 From: tokoko Date: Sat, 14 Feb 2026 10:02:42 +0400 Subject: [PATCH 1/5] feat(java/driver/jni): metadata methods, validation suite --- java/driver/jni-validation-sqlite/pom.xml | 72 ++++++++ .../jni/JniSqliteConnectionMetadataTest.java | 70 ++++++++ .../driver/jni/JniSqliteConnectionTest.java | 28 ++++ .../adbc/driver/jni/JniSqliteQuirks.java | 39 +++++ .../driver/jni/JniSqliteStatementTest.java | 61 +++++++ .../driver/jni/JniSqliteTransactionTest.java | 30 ++++ java/driver/jni/src/main/cpp/jni_wrapper.cc | 156 ++++++++++++++++++ .../arrow/adbc/driver/jni/JniConnection.java | 53 +++++- .../arrow/adbc/driver/jni/JniStatement.java | 14 +- .../arrow/adbc/driver/jni/impl/JniLoader.java | 36 ++++ .../adbc/driver/jni/impl/NativeAdbc.java | 18 ++ .../arrow/adbc/driver/jni/JniDriverTest.java | 28 ++++ java/pom.xml | 6 + 13 files changed, 608 insertions(+), 3 deletions(-) create mode 100644 java/driver/jni-validation-sqlite/pom.xml create mode 100644 java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteConnectionMetadataTest.java create mode 100644 java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteConnectionTest.java create mode 100644 java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteQuirks.java create mode 100644 java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteStatementTest.java create mode 100644 java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteTransactionTest.java diff --git a/java/driver/jni-validation-sqlite/pom.xml b/java/driver/jni-validation-sqlite/pom.xml new file mode 100644 index 0000000000..f50d8a1116 --- /dev/null +++ b/java/driver/jni-validation-sqlite/pom.xml @@ -0,0 +1,72 @@ + + + + 4.0.0 + + org.apache.arrow.adbc + arrow-adbc-java-root + 0.23.0-SNAPSHOT + ../../pom.xml + + + adbc-driver-jni-validation-sqlite + jar + Arrow ADBC Driver JNI Validation with SQLite + Tests validating the JNI driver against SQLite. + + + true + + + + + org.apache.arrow.adbc + adbc-core + test + + + org.apache.arrow.adbc + adbc-driver-jni + test + + + + + org.apache.arrow + arrow-memory-netty + test + + + org.assertj + assertj-core + test + + + org.junit.jupiter + junit-jupiter + test + + + org.apache.arrow.adbc + adbc-driver-validation + test + + + 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 0000000000..e805a15301 --- /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 instead of Int32") + public void getInfo() {} + + @Override + @Disabled("SQLite getInfo schema differs: map value type is List 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-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteConnectionTest.java b/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteConnectionTest.java new file mode 100644 index 0000000000..e15182bca6 --- /dev/null +++ b/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteConnectionTest.java @@ -0,0 +1,28 @@ +/* + * 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.AbstractConnectionTest; +import org.junit.jupiter.api.BeforeAll; + +public class JniSqliteConnectionTest extends AbstractConnectionTest { + @BeforeAll + static void beforeAll() { + quirks = new JniSqliteQuirks(); + } +} diff --git a/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteQuirks.java b/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteQuirks.java new file mode 100644 index 0000000000..6d6124d43c --- /dev/null +++ b/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteQuirks.java @@ -0,0 +1,39 @@ +/* + * 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 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 class JniSqliteQuirks extends SqlValidationQuirks { + @Override + public AdbcDatabase initDatabase(BufferAllocator allocator) throws AdbcException { + final Map parameters = new HashMap<>(); + JniDriver.PARAM_DRIVER.set(parameters, "adbc_driver_sqlite"); + return new JniDriver(allocator).open(parameters); + } + + @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 0000000000..0f09213f85 --- /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-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteTransactionTest.java b/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteTransactionTest.java new file mode 100644 index 0000000000..18a9850c2f --- /dev/null +++ b/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteTransactionTest.java @@ -0,0 +1,30 @@ +/* + * 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.AbstractTransactionTest; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; + +@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 fdfea15b02..2fa5e4ce46 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 #include #include +#include #include #include @@ -445,4 +446,159 @@ 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(static_cast(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 table_type_strings; + std::vector 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(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(); + } + + auto out = std::make_unique(); + std::memset(out.get(), 0, sizeof(struct ArrowArrayStream)); + + CHECK_ADBC_ERROR( + AdbcConnectionGetObjects( + conn, static_cast(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.get(), &error), + error); + + jclass native_result_class = RequireImplClass(env, "NativeQueryResult"); + jmethodID native_result_ctor = + RequireMethod(env, native_result_class, "", "(JJ)V"); + jobject object = + env->NewObject(native_result_class, native_result_ctor, static_cast(-1), + static_cast(reinterpret_cast(out.get()))); + out.release(); + return object; + } 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(static_cast(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 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(info_codes_vec.data())); + c_info_codes = info_codes_vec.data(); + info_codes_length = static_cast(len); + } + + auto out = std::make_unique(); + std::memset(out.get(), 0, sizeof(struct ArrowArrayStream)); + + CHECK_ADBC_ERROR( + AdbcConnectionGetInfo(conn, c_info_codes, info_codes_length, out.get(), &error), + error); + + jclass native_result_class = RequireImplClass(env, "NativeQueryResult"); + jmethodID native_result_ctor = + RequireMethod(env, native_result_class, "", "(JJ)V"); + jobject object = + env->NewObject(native_result_class, native_result_ctor, static_cast(-1), + static_cast(reinterpret_cast(out.get()))); + out.release(); + return object; + } catch (const AdbcException& e) { + e.ThrowJavaException(env); + } + return nullptr; +} + +JNIEXPORT jlong 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(static_cast(handle)); + + auto catalog_str = MaybeGetJniString(env, catalog); + auto db_schema_str = MaybeGetJniString(env, db_schema); + JniStringView table_name_str(env, table_name); + + auto schema = std::make_unique(); + std::memset(schema.get(), 0, sizeof(struct ArrowSchema)); + + 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.get(), &error), + error); + + jlong result = static_cast(reinterpret_cast(schema.get())); + schema.release(); + return result; + } catch (const AdbcException& e) { + e.ThrowJavaException(env); + } + return 0; +} + +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(static_cast(handle)); + + auto out = std::make_unique(); + std::memset(out.get(), 0, sizeof(struct ArrowArrayStream)); + + CHECK_ADBC_ERROR(AdbcConnectionGetTableTypes(conn, out.get(), &error), error); + + jclass native_result_class = RequireImplClass(env, "NativeQueryResult"); + jmethodID native_result_ctor = + RequireMethod(env, native_result_class, "", "(JJ)V"); + jobject object = + env->NewObject(native_result_class, native_result_ctor, static_cast(-1), + static_cast(reinterpret_cast(out.get()))); + out.release(); + return object; + } 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 7c1b9a4a60..f3060156a3 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 @@ -23,9 +23,15 @@ import org.apache.arrow.adbc.core.BulkIngestMode; import org.apache.arrow.adbc.driver.jni.impl.JniLoader; import org.apache.arrow.adbc.driver.jni.impl.NativeConnectionHandle; +import org.apache.arrow.adbc.driver.jni.impl.NativeQueryResult; import org.apache.arrow.adbc.driver.jni.impl.NativeStatementHandle; +import org.apache.arrow.c.ArrowArrayStream; +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.ipc.ArrowReader; +import org.apache.arrow.vector.types.pojo.Schema; import org.checkerframework.checker.nullness.qual.Nullable; public class JniConnection implements AdbcConnection { @@ -76,9 +82,54 @@ public AdbcStatement bulkIngest(String targetTableName, BulkIngestMode mode) } } + private ArrowReader importStream(NativeQueryResult result) { + try (final ArrowArrayStream cStream = ArrowArrayStream.wrap(result.cDataStream())) { + return Data.importArrayStream(allocator, cStream); + } + } + @Override public ArrowReader getInfo(int @Nullable [] infoCodes) throws AdbcException { - throw new UnsupportedOperationException(); + NativeQueryResult result = JniLoader.INSTANCE.connectionGetInfo(handle, infoCodes); + return importStream(result); + } + + @Override + public ArrowReader getObjects( + GetObjectsDepth depth, + String catalogPattern, + String dbSchemaPattern, + String tableNamePattern, + String[] tableTypes, + String columnNamePattern) + throws AdbcException { + NativeQueryResult result = + JniLoader.INSTANCE.connectionGetObjects( + handle, + depth.ordinal(), + catalogPattern, + dbSchemaPattern, + tableNamePattern, + tableTypes, + columnNamePattern); + return importStream(result); + } + + @Override + public Schema getTableSchema(String catalog, String dbSchema, String tableName) + throws AdbcException { + long schemaAddress = + JniLoader.INSTANCE.connectionGetTableSchema(handle, catalog, dbSchema, tableName); + try (final ArrowSchema cSchema = ArrowSchema.wrap(schemaAddress); + final CDataDictionaryProvider provider = new CDataDictionaryProvider()) { + return Data.importSchema(allocator, cSchema, provider); + } + } + + @Override + public ArrowReader getTableTypes() throws AdbcException { + NativeQueryResult result = JniLoader.INSTANCE.connectionGetTableTypes(handle); + return importStream(result); } @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 b496049e19..aa3d2856cd 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 @@ -33,6 +33,7 @@ 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 +47,18 @@ public void setSqlQuery(String query) throws AdbcException { @Override public void bind(VectorSchemaRoot root) throws AdbcException { + this.bindRoot = root; + } + + 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,6 +66,7 @@ public void bind(VectorSchemaRoot root) throws AdbcException { @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; @@ -69,6 +78,7 @@ public QueryResult executeQuery() throws AdbcException { @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 db81428673..a860f37262 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 void statementSetOption(NativeStatementHandle statement, String key, Stri 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 long 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 4e839c0b69..5aea88926d 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 @@ static native NativeDatabaseHandle openDatabase(int version, String[] parameters 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 long 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/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 94fd9394e1..a29f5de0b6 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.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 @@ void preparedStatement() throws Exception { } } + @Test + void getObjects() throws Exception { + try (final BufferAllocator allocator = new RootAllocator()) { + JniDriver driver = new JniDriver(allocator); + Map 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 ed50443e53..0a893e1d33 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -138,6 +138,11 @@ adbc-driver-validation ${project.version} + + org.apache.arrow.adbc + adbc-driver-jni + ${project.version} + org.apache.arrow.adbc adbc-driver-manager @@ -328,6 +333,7 @@ jni driver/jni + driver/jni-validation-sqlite From 623ae7705ff7e0f5a5d4eaea4456a4784acae201 Mon Sep 17 00:00:00 2001 From: tokoko Date: Mon, 16 Feb 2026 17:46:05 +0400 Subject: [PATCH 2/5] fix: refactor importStream, add bind comment --- .../arrow/adbc/driver/jni/JniConnection.java | 22 +++++-------------- .../arrow/adbc/driver/jni/JniStatement.java | 12 ++++------ .../driver/jni/impl/NativeQueryResult.java | 12 ++++++++++ 3 files changed, 22 insertions(+), 24 deletions(-) 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 f3060156a3..284810e8e2 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 @@ -23,9 +23,7 @@ import org.apache.arrow.adbc.core.BulkIngestMode; import org.apache.arrow.adbc.driver.jni.impl.JniLoader; import org.apache.arrow.adbc.driver.jni.impl.NativeConnectionHandle; -import org.apache.arrow.adbc.driver.jni.impl.NativeQueryResult; import org.apache.arrow.adbc.driver.jni.impl.NativeStatementHandle; -import org.apache.arrow.c.ArrowArrayStream; import org.apache.arrow.c.ArrowSchema; import org.apache.arrow.c.CDataDictionaryProvider; import org.apache.arrow.c.Data; @@ -82,16 +80,9 @@ public AdbcStatement bulkIngest(String targetTableName, BulkIngestMode mode) } } - private ArrowReader importStream(NativeQueryResult result) { - try (final ArrowArrayStream cStream = ArrowArrayStream.wrap(result.cDataStream())) { - return Data.importArrayStream(allocator, cStream); - } - } - @Override public ArrowReader getInfo(int @Nullable [] infoCodes) throws AdbcException { - NativeQueryResult result = JniLoader.INSTANCE.connectionGetInfo(handle, infoCodes); - return importStream(result); + return JniLoader.INSTANCE.connectionGetInfo(handle, infoCodes).importStream(allocator); } @Override @@ -103,16 +94,16 @@ public ArrowReader getObjects( String[] tableTypes, String columnNamePattern) throws AdbcException { - NativeQueryResult result = - JniLoader.INSTANCE.connectionGetObjects( + return JniLoader.INSTANCE + .connectionGetObjects( handle, depth.ordinal(), catalogPattern, dbSchemaPattern, tableNamePattern, tableTypes, - columnNamePattern); - return importStream(result); + columnNamePattern) + .importStream(allocator); } @Override @@ -128,8 +119,7 @@ public Schema getTableSchema(String catalog, String dbSchema, String tableName) @Override public ArrowReader getTableTypes() throws AdbcException { - NativeQueryResult result = JniLoader.INSTANCE.connectionGetTableTypes(handle); - return importStream(result); + 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 aa3d2856cd..5a68fb4660 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,12 +23,10 @@ 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; @@ -50,6 +48,9 @@ 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; @@ -68,12 +69,7 @@ private void exportBind() throws AdbcException { 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 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 526d615c3b..6fc51cb372 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,6 +17,11 @@ 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; @@ -33,4 +38,11 @@ public long 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.wrap(cDataStream)) { + return Data.importArrayStream(allocator, cStream); + } + } } From 3ca0d6e90c5b77835bf3613dac2a6bb4836d10a9 Mon Sep 17 00:00:00 2001 From: tokoko Date: Tue, 17 Feb 2026 12:26:37 +0400 Subject: [PATCH 3/5] feat: java takes ownership with snapshot --- java/driver/jni/src/main/cpp/jni_wrapper.cc | 66 ++++++------------- .../driver/jni/impl/NativeQueryResult.java | 14 ++-- 2 files changed, 28 insertions(+), 52 deletions(-) diff --git a/java/driver/jni/src/main/cpp/jni_wrapper.cc b/java/driver/jni/src/main/cpp/jni_wrapper.cc index 2fa5e4ce46..2188ad869a 100644 --- a/java/driver/jni/src/main/cpp/jni_wrapper.cc +++ b/java/driver/jni/src/main/cpp/jni_wrapper.cc @@ -335,27 +335,27 @@ 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, "", "(JJ)V"); + return env->NewObject(native_result_class, native_result_ctor, rows_affected, + static_cast(reinterpret_cast(out))); +} + 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(static_cast(handle)); - auto out = std::make_unique(); - 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), + 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, "", "(JJ)V"); - jobject object = - env->NewObject(native_result_class, native_result_ctor, rows_affected, - static_cast(reinterpret_cast(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); } @@ -480,25 +480,17 @@ Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetObjects( c_table_types = table_type_ptrs.data(); } - auto out = std::make_unique(); - std::memset(out.get(), 0, sizeof(struct ArrowArrayStream)); + struct ArrowArrayStream out = {}; CHECK_ADBC_ERROR( AdbcConnectionGetObjects( conn, static_cast(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.get(), &error), + column_name_str ? column_name_str->c_str() : nullptr, &out, &error), error); - jclass native_result_class = RequireImplClass(env, "NativeQueryResult"); - jmethodID native_result_ctor = - RequireMethod(env, native_result_class, "", "(JJ)V"); - jobject object = - env->NewObject(native_result_class, native_result_ctor, static_cast(-1), - static_cast(reinterpret_cast(out.get()))); - out.release(); - return object; + return MakeNativeQueryResult(env, -1, &out); } catch (const AdbcException& e) { e.ThrowJavaException(env); } @@ -525,21 +517,13 @@ Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetInfo( info_codes_length = static_cast(len); } - auto out = std::make_unique(); - std::memset(out.get(), 0, sizeof(struct ArrowArrayStream)); + struct ArrowArrayStream out = {}; CHECK_ADBC_ERROR( - AdbcConnectionGetInfo(conn, c_info_codes, info_codes_length, out.get(), &error), + AdbcConnectionGetInfo(conn, c_info_codes, info_codes_length, &out, &error), error); - jclass native_result_class = RequireImplClass(env, "NativeQueryResult"); - jmethodID native_result_ctor = - RequireMethod(env, native_result_class, "", "(JJ)V"); - jobject object = - env->NewObject(native_result_class, native_result_ctor, static_cast(-1), - static_cast(reinterpret_cast(out.get()))); - out.release(); - return object; + return MakeNativeQueryResult(env, -1, &out); } catch (const AdbcException& e) { e.ThrowJavaException(env); } @@ -583,19 +567,11 @@ Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetTableTypes( struct AdbcError error = ADBC_ERROR_INIT; auto* conn = reinterpret_cast(static_cast(handle)); - auto out = std::make_unique(); - std::memset(out.get(), 0, sizeof(struct ArrowArrayStream)); + struct ArrowArrayStream out = {}; - CHECK_ADBC_ERROR(AdbcConnectionGetTableTypes(conn, out.get(), &error), error); + CHECK_ADBC_ERROR(AdbcConnectionGetTableTypes(conn, &out, &error), error); - jclass native_result_class = RequireImplClass(env, "NativeQueryResult"); - jmethodID native_result_ctor = - RequireMethod(env, native_result_class, "", "(JJ)V"); - jobject object = - env->NewObject(native_result_class, native_result_ctor, static_cast(-1), - static_cast(reinterpret_cast(out.get()))); - out.release(); - return object; + return MakeNativeQueryResult(env, -1, &out); } catch (const AdbcException& e) { e.ThrowJavaException(env); } 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 6fc51cb372..6399dfbb36 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 @@ -24,24 +24,24 @@ 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.wrap(cDataStream)) { + try (final ArrowArrayStream cStream = ArrowArrayStream.allocateNew(allocator)) { + cStream.save(streamSnapshot); return Data.importArrayStream(allocator, cStream); } } From 20ba4fd20210a1ced6c80b5fde1aa74d474192fd Mon Sep 17 00:00:00 2001 From: tokoko Date: Tue, 17 Feb 2026 12:43:13 +0400 Subject: [PATCH 4/5] chore: precommit --- java/driver/jni/src/main/cpp/jni_wrapper.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/java/driver/jni/src/main/cpp/jni_wrapper.cc b/java/driver/jni/src/main/cpp/jni_wrapper.cc index 2188ad869a..99c0c30d24 100644 --- a/java/driver/jni/src/main/cpp/jni_wrapper.cc +++ b/java/driver/jni/src/main/cpp/jni_wrapper.cc @@ -352,8 +352,7 @@ Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementExecuteQuery( auto* ptr = reinterpret_cast(static_cast(handle)); struct ArrowArrayStream out = {}; int64_t rows_affected = 0; - CHECK_ADBC_ERROR(AdbcStatementExecuteQuery(ptr, &out, &rows_affected, &error), - error); + CHECK_ADBC_ERROR(AdbcStatementExecuteQuery(ptr, &out, &rows_affected, &error), error); return MakeNativeQueryResult(env, rows_affected, &out); } catch (const AdbcException& e) { From 8d10ce4cd70088d1b8bb7ed863dddebd2edcda96 Mon Sep 17 00:00:00 2001 From: tokoko Date: Wed, 18 Feb 2026 11:09:24 +0400 Subject: [PATCH 5/5] feat: add NativeSchemaResult --- java/driver/jni/src/main/cpp/jni_wrapper.cc | 21 +++++---- .../arrow/adbc/driver/jni/JniConnection.java | 12 ++--- .../arrow/adbc/driver/jni/impl/JniLoader.java | 2 +- .../adbc/driver/jni/impl/NativeAdbc.java | 2 +- .../driver/jni/impl/NativeSchemaResult.java | 44 +++++++++++++++++++ 5 files changed, 62 insertions(+), 19 deletions(-) create mode 100644 java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeSchemaResult.java diff --git a/java/driver/jni/src/main/cpp/jni_wrapper.cc b/java/driver/jni/src/main/cpp/jni_wrapper.cc index 99c0c30d24..eb2564c5a5 100644 --- a/java/driver/jni/src/main/cpp/jni_wrapper.cc +++ b/java/driver/jni/src/main/cpp/jni_wrapper.cc @@ -344,6 +344,14 @@ jobject MakeNativeQueryResult(JNIEnv* env, jlong rows_affected, static_cast(reinterpret_cast(out))); } +jobject MakeNativeSchemaResult(JNIEnv* env, struct ArrowSchema* schema) { + jclass native_result_class = RequireImplClass(env, "NativeSchemaResult"); + jmethodID native_result_ctor = + RequireMethod(env, native_result_class, "", "(J)V"); + return env->NewObject(native_result_class, native_result_ctor, + static_cast(reinterpret_cast(schema))); +} + JNIEXPORT jobject JNICALL Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_statementExecuteQuery( JNIEnv* env, [[maybe_unused]] jclass self, jlong handle) { @@ -529,7 +537,7 @@ Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetInfo( return nullptr; } -JNIEXPORT jlong JNICALL +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) { @@ -541,22 +549,19 @@ Java_org_apache_arrow_adbc_driver_jni_impl_NativeAdbc_connectionGetTableSchema( auto db_schema_str = MaybeGetJniString(env, db_schema); JniStringView table_name_str(env, table_name); - auto schema = std::make_unique(); - std::memset(schema.get(), 0, sizeof(struct ArrowSchema)); + 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.get(), &error), + table_name_str.value, &schema, &error), error); - jlong result = static_cast(reinterpret_cast(schema.get())); - schema.release(); - return result; + return MakeNativeSchemaResult(env, &schema); } catch (const AdbcException& e) { e.ThrowJavaException(env); } - return 0; + return nullptr; } JNIEXPORT jobject JNICALL 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 284810e8e2..ef214eea32 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 @@ -24,9 +24,6 @@ import org.apache.arrow.adbc.driver.jni.impl.JniLoader; import org.apache.arrow.adbc.driver.jni.impl.NativeConnectionHandle; import org.apache.arrow.adbc.driver.jni.impl.NativeStatementHandle; -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.ipc.ArrowReader; import org.apache.arrow.vector.types.pojo.Schema; @@ -109,12 +106,9 @@ public ArrowReader getObjects( @Override public Schema getTableSchema(String catalog, String dbSchema, String tableName) throws AdbcException { - long schemaAddress = - JniLoader.INSTANCE.connectionGetTableSchema(handle, catalog, dbSchema, tableName); - try (final ArrowSchema cSchema = ArrowSchema.wrap(schemaAddress); - final CDataDictionaryProvider provider = new CDataDictionaryProvider()) { - return Data.importSchema(allocator, cSchema, provider); - } + return JniLoader.INSTANCE + .connectionGetTableSchema(handle, catalog, dbSchema, tableName) + .importSchema(allocator); } @Override 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 a860f37262..a885e2ae2b 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 @@ -143,7 +143,7 @@ public NativeQueryResult connectionGetInfo(NativeConnectionHandle connection, in return NativeAdbc.connectionGetInfo(connection.getConnectionHandle(), infoCodes); } - public long connectionGetTableSchema( + public NativeSchemaResult connectionGetTableSchema( NativeConnectionHandle connection, String catalog, String dbSchema, String tableName) throws AdbcException { return NativeAdbc.connectionGetTableSchema( 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 5aea88926d..199662f156 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 @@ -65,7 +65,7 @@ static native NativeQueryResult connectionGetObjects( static native NativeQueryResult connectionGetInfo(long handle, int[] infoCodes) throws AdbcException; - static native long connectionGetTableSchema( + 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/NativeSchemaResult.java b/java/driver/jni/src/main/java/org/apache/arrow/adbc/driver/jni/impl/NativeSchemaResult.java new file mode 100644 index 0000000000..a09d3894b1 --- /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); + } + } +}