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..eb2564c5a5 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 @@ -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, "", "(JJ)V"); + return env->NewObject(native_result_class, native_result_ctor, 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) { 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), - 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); } @@ -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(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(); + } + + 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, &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(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); + } + + 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(static_cast(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(static_cast(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 7c1b9a4a60..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 @@ -26,6 +26,7 @@ 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 AdbcStatement bulkIngest(String targetTableName, BulkIngestMode mode) @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 b496049e19..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,16 +23,15 @@ 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 void setSqlQuery(String query) throws AdbcException { @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 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; - 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 db81428673..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 @@ -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 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 4e839c0b69..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 @@ -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 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 526d615c3b..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 @@ -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 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); + } + } +} 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