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 6c80fa306 test(java/driver/jni): add tests with drivers besides SQLite
(#4235)
6c80fa306 is described below
commit 6c80fa306fbe21448903aaaf9a48f6db15457dfd
Author: David Li <[email protected]>
AuthorDate: Thu Apr 23 07:47:17 2026 +0900
test(java/driver/jni): add tests with drivers besides SQLite (#4235)
Closes #4230.
---
.env | 1 +
.github/workflows/java.yml | 30 +-
CONTRIBUTING.md | 1 +
.../pom.xml | 2 +-
.../jni/JniSqliteConnectionMetadataTest.java | 0
.../adbc/driver/jni/JniSqliteConnectionTest.java | 12 +
.../arrow/adbc/driver/jni/JniSqliteQuirks.java | 0
.../adbc/driver/jni/JniSqliteStatementTest.java | 0
.../adbc/driver/jni/JniSqliteTransactionTest.java | 0
.../adbc/driver/jni/PostgresIntegrationTest.java | 534 +++++++++++++++++++++
.../adbc/driver/jni/SqlServerIntegrationTest.java | 527 ++++++++++++++++++++
.../driver/testsuite/AbstractConnectionTest.java | 8 +-
.../arrow/adbc/driver/testsuite/ArrowToJava.java | 287 +++++++++++
java/pom.xml | 2 +-
14 files changed, 1395 insertions(+), 9 deletions(-)
diff --git a/.env b/.env
index 607f2f52c..5a328374d 100644
--- a/.env
+++ b/.env
@@ -55,6 +55,7 @@ ADBC_JDBC_POSTGRESQL_URL=localhost:5432/postgres
ADBC_JDBC_POSTGRESQL_USER=postgres
ADBC_JDBC_POSTGRESQL_PASSWORD=password
ADBC_JDBC_POSTGRESQL_DATABASE=postgres
+ADBC_MSSQL_TEST_URI="sqlserver://SA:Password1!@localhost:1433"
ADBC_POSTGRESQL_TEST_URI="postgresql://localhost:5432/postgres?user=postgres&password=password"
ADBC_SQLITE_FLIGHTSQL_URI=grpc+tcp://localhost:8080
ADBC_TEST_FLIGHTSQL_URI=grpc+tls://localhost:41414
diff --git a/.github/workflows/java.yml b/.github/workflows/java.yml
index df5173bc7..541a6eb82 100644
--- a/.github/workflows/java.yml
+++ b/.github/workflows/java.yml
@@ -219,7 +219,7 @@ jobs:
cache: "maven"
distribution: "temurin"
java-version: 11
- - name: Build/Test
+ - name: Build
run: |
set -x
pushd artifacts
@@ -228,10 +228,34 @@ jobs:
done
popd
cp -r artifacts/*/jni/adbc_driver_jni
java/driver/jni/src/main/resources
+ env BUILD_JNI=ON ./ci/scripts/java_build.sh $(pwd)
+
+ - name: Start Dependencies
+ if: matrix.os == 'Linux' && matrix.arch == 'amd64'
+ run: |
+ docker compose up --detach --wait mssql-test postgres-test
+ cat .env | grep -v -e '^#' | grep -e '^ADBC_' | awk NF | sed
's/"//g' | tee -a $GITHUB_ENV
+
+ - name: Download thirdparty driver
+ if: matrix.os == 'Linux' && matrix.arch == 'amd64'
+ run: |
+ wget
https://dbc-cdn.columnar.tech/mssql/v1.3.1/mssql_linux_amd64_v1.3.1.tar.gz
+ echo
"e6723cf417403f313fb75c1ac03aea9b9ff857d4a947608c8ae44eacc1aa22b3
mssql_linux_amd64_v1.3.1.tar.gz" > mssql_linux_amd64_v1.3.1.tar.gz.sha256
+ sha256sum -c mssql_linux_amd64_v1.3.1.tar.gz.sha256
+
+ tar xvf mssql_linux_amd64_v1.3.1.tar.gz
+ mkdir -p ~/.config/adbc/drivers/
+ mv libadbc_driver_mssql.so ~/.config/adbc/drivers/
+ echo "manifest_version = 1" > ~/.config/adbc/drivers/mssql.toml
+ echo "[Driver]" >> ~/.config/adbc/drivers/mssql.toml
+ echo "shared = '$HOME/.config/adbc/drivers/libadbc_driver_mssql.so'"
>> ~/.config/adbc/drivers/mssql.toml
+ cat ~/.config/adbc/drivers/mssql.toml
+
+ - name: Test
+ run: |
for driver in artifacts/*/driver; do
export LD_LIBRARY_PATH=$(pwd)/$driver:${LD_LIBRARY_PATH:-}
export DYLD_LIBRARY_PATH=$(pwd)/$driver:${DYLD_LIBRARY_PATH:-}
done
- env BUILD_JNI=ON ./ci/scripts/java_build.sh $(pwd)
cd java
- mvn -B -Pjni test -pl :adbc-driver-jni
+ mvn -B -Pjni test -pl :adbc-driver-jni -pl
:adbc-driver-jni-validation
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f177e04fc..bc4ce23f1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -361,6 +361,7 @@ export ADBC_USE_ASAN=OFF
export ADBC_USE_UBSAN=OFF
export BUILD_ALL=OFF
export BUILD_DRIVER_MANAGER=ON
+export BUILD_DRIVER_POSTGRESQL=ON
export BUILD_DRIVER_SQLITE=ON
./ci/scripts/cpp_build.sh $(pwd) $(pwd)/build $(pwd)/local
diff --git a/java/driver/jni-validation-sqlite/pom.xml
b/java/driver/jni-validation/pom.xml
similarity index 97%
rename from java/driver/jni-validation-sqlite/pom.xml
rename to java/driver/jni-validation/pom.xml
index 20d121150..148f84785 100644
--- a/java/driver/jni-validation-sqlite/pom.xml
+++ b/java/driver/jni-validation/pom.xml
@@ -26,7 +26,7 @@
<relativePath>../../pom.xml</relativePath>
</parent>
- <artifactId>adbc-driver-jni-validation-sqlite</artifactId>
+ <artifactId>adbc-driver-jni-validation</artifactId>
<packaging>jar</packaging>
<name>Arrow ADBC Driver JNI Validation with SQLite</name>
<description>Tests validating the JNI driver against SQLite.</description>
diff --git
a/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteConnectionMetadataTest.java
b/java/driver/jni-validation/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteConnectionMetadataTest.java
similarity index 100%
rename from
java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteConnectionMetadataTest.java
rename to
java/driver/jni-validation/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteConnectionMetadataTest.java
diff --git
a/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteConnectionTest.java
b/java/driver/jni-validation/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteConnectionTest.java
similarity index 84%
rename from
java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteConnectionTest.java
rename to
java/driver/jni-validation/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteConnectionTest.java
index e15182bca..6b1150716 100644
---
a/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteConnectionTest.java
+++
b/java/driver/jni-validation/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteConnectionTest.java
@@ -19,10 +19,22 @@ package org.apache.arrow.adbc.driver.jni;
import org.apache.arrow.adbc.driver.testsuite.AbstractConnectionTest;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
public class JniSqliteConnectionTest extends AbstractConnectionTest {
@BeforeAll
static void beforeAll() {
quirks = new JniSqliteQuirks();
}
+
+ @Test
+ @Disabled
+ @Override
+ protected void readOnly() {}
+
+ @Test
+ @Disabled
+ @Override
+ protected void isolationLevel() {}
}
diff --git
a/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteQuirks.java
b/java/driver/jni-validation/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteQuirks.java
similarity index 100%
rename from
java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteQuirks.java
rename to
java/driver/jni-validation/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteQuirks.java
diff --git
a/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteStatementTest.java
b/java/driver/jni-validation/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteStatementTest.java
similarity index 100%
rename from
java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteStatementTest.java
rename to
java/driver/jni-validation/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteStatementTest.java
diff --git
a/java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteTransactionTest.java
b/java/driver/jni-validation/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteTransactionTest.java
similarity index 100%
rename from
java/driver/jni-validation-sqlite/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteTransactionTest.java
rename to
java/driver/jni-validation/src/test/java/org/apache/arrow/adbc/driver/jni/JniSqliteTransactionTest.java
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
new file mode 100644
index 000000000..b805a420d
--- /dev/null
+++
b/java/driver/jni-validation/src/test/java/org/apache/arrow/adbc/driver/jni/PostgresIntegrationTest.java
@@ -0,0 +1,534 @@
+/*
+ * 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 static
org.apache.arrow.adbc.driver.testsuite.ArrowAssertions.assertSchema;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.apache.arrow.adbc.core.AdbcConnection;
+import org.apache.arrow.adbc.core.AdbcDatabase;
+import org.apache.arrow.adbc.core.AdbcDriver;
+import org.apache.arrow.adbc.core.AdbcException;
+import org.apache.arrow.adbc.core.AdbcInfoCode;
+import org.apache.arrow.adbc.core.AdbcOptions;
+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.TypedKey;
+import org.apache.arrow.adbc.driver.testsuite.ArrowToJava;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.memory.RootAllocator;
+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.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;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assumptions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class PostgresIntegrationTest {
+ public static final String URI_ENV = "ADBC_POSTGRESQL_TEST_URI";
+ static String URI = System.getenv(URI_ENV);
+
+ BufferAllocator allocator;
+ JniDriver driver;
+ AdbcDatabase db;
+ AdbcConnection conn;
+
+ @BeforeAll
+ static void beforeAll() {
+ Assumptions.assumeFalse(
+ URI == null || URI.isEmpty(),
+ String.format("Must set %s to run Postgres integration tests",
URI_ENV));
+ }
+
+ @BeforeEach
+ void beforeEach() throws Exception {
+ System.err.println("Connecting to PostgreSQL with URI: " + URI);
+ allocator = new RootAllocator();
+ driver = new JniDriver(allocator);
+ Map<String, Object> parameters = new HashMap<>();
+ JniDriver.PARAM_DRIVER.set(parameters, "adbc_driver_postgresql");
+ AdbcDriver.PARAM_URI.set(parameters, URI);
+ db = driver.open(parameters);
+ conn = db.connect();
+ }
+
+ @AfterEach
+ void afterEach() throws Exception {
+ conn.close();
+ db.close();
+ allocator.close();
+ }
+
+ @Test
+ void simple() throws Exception {
+ try (var stmt = conn.createStatement()) {
+ stmt.setSqlQuery("SELECT 1 + 1 AS sum");
+ try (var reader = stmt.executeQuery()) {
+ assertThat(reader.getReader().loadNextBatch()).isTrue();
+
assertThat(reader.getReader().getVectorSchemaRoot().getVector("sum").getObject(0))
+ .isEqualTo(2);
+ }
+ }
+ }
+
+ @Test
+ void options() throws Exception {
+ testOptions(db);
+ testOptions(conn);
+ try (AdbcStatement stmt = conn.createStatement()) {
+ testOptions(stmt);
+ }
+ }
+
+ @Test
+ void connectionGetInfo() throws Exception {
+ try (ArrowReader reader = conn.getInfo()) {
+ assertThat(reader.loadNextBatch()).isTrue();
+
+ var codes =
ArrowToJava.toIntegers(reader.getVectorSchemaRoot().getVector("info_name"));
+ var values =
ArrowToJava.toObjects(reader.getVectorSchemaRoot().getVector("info_value"));
+ var infos =
+ IntStream.range(0, codes.size())
+ .mapToObj(i -> Map.entry(codes.get(i), values.get(i)))
+ .collect(Collectors.toMap(Map.Entry::getKey,
Map.Entry::getValue));
+
+ assertThat(codes).contains(AdbcInfoCode.VENDOR_NAME.getValue());
+ assertThat(codes).contains(AdbcInfoCode.VENDOR_VERSION.getValue());
+ assertThat(codes).contains(AdbcInfoCode.DRIVER_NAME.getValue());
+ assertThat(codes).contains(AdbcInfoCode.DRIVER_VERSION.getValue());
+
assertThat(infos.get(AdbcInfoCode.VENDOR_NAME.getValue())).isEqualTo("PostgreSQL");
+ assertThat(infos.get(AdbcInfoCode.DRIVER_NAME.getValue()))
+ .isEqualTo("ADBC PostgreSQL Driver");
+
+ assertThat(reader.loadNextBatch()).isFalse();
+ }
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void connectionGetObjects() throws Exception {
+ runSetup(
+ "DROP SCHEMA IF EXISTS test_schema CASCADE",
+ "DROP TABLE IF EXISTS public.foobar",
+ "CREATE SCHEMA test_schema",
+ "CREATE TABLE test_schema.foobar (a INT)",
+ "CREATE TABLE public.foobar (b TEXT)");
+
+ try (var reader =
+ conn.getObjects(
+ AdbcConnection.GetObjectsDepth.ALL, null, "test_schema", null,
null, null)) {
+ var values = ArrowToJava.toObjects(reader, "catalog_db_schemas");
+ var schemas = (List<?>) values.get(0);
+ var schema = (Map<String, ?>) schemas.get(0);
+
assertThat(schema).extractingByKey("db_schema_name").isEqualTo("test_schema");
+ var tables = (List<?>) schema.get("db_schema_tables");
+ assertThat(tables).size().isEqualTo(1);
+ var table = (Map<String, ?>) tables.get(0);
+ assertThat(table).extractingByKey("table_name").isEqualTo("foobar");
+ assertThat(table).extractingByKey("table_type").isEqualTo("table");
+ var columns = (List<Map<String, ?>>) table.get("table_columns");
+
assertThat(columns).singleElement().extracting("column_name").isEqualTo("a");
+ }
+ }
+
+ @Test
+ void connectionGetTableSchema() throws Exception {
+ runSetup(
+ "DROP SCHEMA IF EXISTS test_schema CASCADE",
+ "DROP TABLE IF EXISTS public.foobar",
+ "CREATE SCHEMA test_schema",
+ "CREATE TABLE test_schema.foobar (a INT)",
+ "CREATE TABLE public.foobar (b TEXT)");
+
+ assertSchema(conn.getTableSchema(null, "test_schema", "foobar"))
+ .isEqualTo(new Schema(List.of(Field.nullable("a",
Types.MinorType.INT.getType()))));
+ assertSchema(conn.getTableSchema(null, null, "foobar"))
+ .isEqualTo(new Schema(List.of(Field.nullable("b",
Types.MinorType.VARCHAR.getType()))));
+ }
+
+ @Test
+ void connectionTableTypes() throws Exception {
+ try (ArrowReader reader = conn.getTableTypes()) {
+ List<String> tableTypes = ArrowToJava.toStrings(reader, "table_type");
+ assertThat(tableTypes)
+ .containsExactlyInAnyOrder(
+ "partitioned_table",
+ "foreign_table",
+ "toast_table",
+ "materialized_view",
+ "view",
+ "table");
+ }
+ }
+
+ @Test
+ void connectionReadOnly() {
+ AdbcException e = assertThrows(AdbcException.class, () ->
conn.getReadOnly());
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_FOUND);
+
+ e = assertThrows(AdbcException.class, () -> conn.setReadOnly(true));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_IMPLEMENTED);
+ }
+
+ @Test
+ void bulkIngest() throws Exception {
+ runSetup("DROP TABLE IF EXISTS foobar");
+
+ final Schema schema =
+ new Schema(
+ List.of(
+ Field.nullable("index", Types.MinorType.INT.getType()),
+ Field.nullable("value", Types.MinorType.VARCHAR.getType())));
+ try (VectorSchemaRoot vsr = VectorSchemaRoot.create(schema, allocator)) {
+ IntVector iv = (IntVector) vsr.getVector(0);
+ VarCharVector vv = (VarCharVector) vsr.getVector(1);
+
+ try (AdbcStatement stmt = conn.bulkIngest("foobar",
BulkIngestMode.CREATE)) {
+ iv.setSafe(0, 1);
+ iv.setSafe(1, 2);
+ vv.setNull(0);
+ vv.setSafe(1, "foobar".getBytes(StandardCharsets.UTF_8));
+ vsr.setRowCount(2);
+
+ stmt.bind(vsr);
+ assertThat(stmt.executeUpdate().getAffectedRows()).isEqualTo(2);
+ }
+
+ try (AdbcStatement stmt = conn.bulkIngest("foobar",
BulkIngestMode.REPLACE)) {
+ iv.setSafe(0, 1);
+ iv.setSafe(1, 2);
+ vv.setSafe(0, "".getBytes(StandardCharsets.UTF_8));
+ vv.setSafe(1, "testtest".getBytes(StandardCharsets.UTF_8));
+ vsr.setRowCount(2);
+
+ stmt.bind(vsr);
+ assertThat(stmt.executeUpdate().getAffectedRows()).isEqualTo(2);
+ }
+
+ try (AdbcStatement stmt = conn.bulkIngest("foobar",
BulkIngestMode.APPEND)) {
+ iv.setSafe(0, 3);
+ iv.setSafe(1, 4);
+ iv.setSafe(2, 5);
+ vv.setSafe(0, "spam".getBytes(StandardCharsets.UTF_8));
+ vv.setNull(1);
+ vv.setSafe(2, "eggs".getBytes(StandardCharsets.UTF_8));
+ vsr.setRowCount(3);
+
+ stmt.bind(vsr);
+ assertThat(stmt.executeUpdate().getAffectedRows()).isEqualTo(3);
+ }
+
+ try (AdbcStatement stmt = conn.bulkIngest("foobar",
BulkIngestMode.CREATE_APPEND)) {
+ iv.setSafe(0, 6);
+ iv.setSafe(1, 7);
+ iv.setSafe(2, 8);
+ vv.setSafe(0, "spam".getBytes(StandardCharsets.UTF_8));
+ vv.setNull(1);
+ vv.setNull(2);
+ vsr.setRowCount(3);
+
+ stmt.bind(vsr);
+ assertThat(stmt.executeUpdate().getAffectedRows()).isEqualTo(3);
+ }
+ }
+
+ try (AdbcStatement stmt = conn.createStatement()) {
+ stmt.setSqlQuery("SELECT value FROM foobar ORDER BY index");
+ try (var result = stmt.executeQuery()) {
+ var values = ArrowToJava.toStrings(result.getReader(), "value");
+ assertThat(values)
+ .containsExactly("", "testtest", "spam", null, "eggs", "spam",
null, null);
+ }
+ }
+ }
+
+ @Test
+ void currentCatalogSchema() throws Exception {
+ runSetup(
+ "DROP SCHEMA IF EXISTS test_schema CASCADE",
+ "DROP TABLE IF EXISTS public.foobar",
+ "CREATE SCHEMA test_schema",
+ "CREATE TABLE test_schema.foobar (a INT)",
+ "CREATE TABLE public.foobar (b TEXT)");
+ assertThat(conn.getCurrentCatalog()).isEqualTo("postgres");
+ assertThat(conn.getCurrentDbSchema()).isEqualTo("public");
+
+ AdbcException e = assertThrows(AdbcException.class, () ->
conn.setCurrentCatalog("foobar"));
+ assertThat(e).hasMessageContaining("Unknown option");
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_IMPLEMENTED);
+
+ conn.setCurrentDbSchema("test_schema");
+ try (AdbcStatement stmt = conn.createStatement()) {
+ stmt.setSqlQuery("SELECT * FROM foobar");
+ try (AdbcStatement.QueryResult result = stmt.executeQuery()) {
+ assertSchema(result.getReader().getVectorSchemaRoot().getSchema())
+ .isEqualTo(new Schema(List.of(Field.nullable("a",
Types.MinorType.INT.getType()))));
+ }
+ }
+ }
+
+ @Test
+ void transactions() throws Exception {}
+
+ @Test
+ void selectQuery() throws Exception {
+ try (var stmt = conn.createStatement()) {
+ stmt.setSqlQuery("SELECT 42 AS THEANSWER, 'meaning of life' AS
THEQUESTION");
+
+ assertSchema(stmt.executeSchema())
+ .isEqualTo(
+ new Schema(
+ List.of(
+ Field.nullable("theanswer",
Types.MinorType.INT.getType()),
+ Field.nullable("thequestion",
Types.MinorType.VARCHAR.getType()))));
+
+ try (AdbcStatement.QueryResult result = stmt.executeQuery()) {
+ assertThat(result.getReader().loadNextBatch()).isTrue();
+ assertThat(result.getReader().loadNextBatch()).isFalse();
+ }
+ }
+ // TODO(https://github.com/apache/arrow-adbc/issues/4239): test get
parameter schema
+ }
+
+ @Test
+ void updateQuery() throws Exception {
+ runSetup("DROP TABLE IF EXISTS foobar", "CREATE TABLE foobar (i INT)");
+
+ try (var stmt = conn.createStatement()) {
+ stmt.setSqlQuery("INSERT INTO foobar VALUES (1), (2)");
+ AdbcStatement.UpdateResult result = stmt.executeUpdate();
+ assertThat(result.getAffectedRows()).isEqualTo(2);
+ }
+ }
+
+ @Test
+ void prepare() throws Exception {
+ try (var stmt = conn.createStatement()) {
+ assertThat(assertThrows(AdbcException.class, stmt::prepare))
+ .hasMessageContaining("Must SetSqlQuery() before Prepare()");
+ }
+ }
+
+ @Test
+ void prepareSelectQuery() throws Exception {
+ runSetup(
+ "DROP TABLE IF EXISTS foobar",
+ "CREATE TABLE foobar (i INT)",
+ "INSERT INTO foobar VALUES (1), (2)");
+
+ try (var stmt = conn.createStatement()) {
+ stmt.setSqlQuery("SELECT i + 2 AS baz FROM foobar ORDER BY baz ASC");
+ stmt.prepare();
+
+ assertSchema(stmt.executeSchema())
+ .isEqualTo(new Schema(List.of(Field.nullable("baz",
Types.MinorType.INT.getType()))));
+
+ try (AdbcStatement.QueryResult result = stmt.executeQuery()) {
+ var values = ArrowToJava.toIntegers(result.getReader(), "baz");
+ assertThat(values).containsExactly(3, 4);
+ }
+
+ try (AdbcStatement.QueryResult result = stmt.executeQuery()) {
+ var values = ArrowToJava.toIntegers(result.getReader(), "baz");
+ assertThat(values).containsExactly(3, 4);
+ }
+ }
+ }
+
+ @Test
+ void prepareUpdateQuery() throws Exception {
+ runSetup("DROP TABLE IF EXISTS foobar", "CREATE TABLE foobar (i INT)");
+
+ try (var stmt = conn.createStatement()) {
+ stmt.setSqlQuery("INSERT INTO foobar VALUES (1), (2)");
+ stmt.prepare();
+
+ AdbcStatement.UpdateResult result = stmt.executeUpdate();
+ assertThat(result.getAffectedRows()).isEqualTo(2);
+
+ result = stmt.executeUpdate();
+ assertThat(result.getAffectedRows()).isEqualTo(2);
+ }
+ }
+
+ @Test
+ void bindSelectQuery() throws Exception {
+ Schema schema = new Schema(List.of(Field.nullable("$1",
Types.MinorType.INT.getType())));
+ try (var stmt = conn.createStatement();
+ VectorSchemaRoot vsr = VectorSchemaRoot.create(schema, allocator)) {
+ stmt.setSqlQuery("SELECT $1 + 1 AS baz");
+ assertSchema(stmt.executeSchema())
+ .isEqualTo(new Schema(List.of(Field.nullable("baz",
Types.MinorType.INT.getType()))));
+
+ var iv = (IntVector) vsr.getVector(0);
+ iv.setSafe(0, 1);
+ iv.setSafe(1, 42);
+ iv.setNull(2);
+ vsr.setRowCount(3);
+ stmt.bind(vsr);
+
+ try (AdbcStatement.QueryResult result = stmt.executeQuery()) {
+ var values = ArrowToJava.toIntegers(result.getReader(), "baz");
+ assertThat(values).containsExactly(2, 43, null);
+ }
+
+ iv.setNull(0);
+ iv.setSafe(1, 401);
+ iv.setSafe(2, 200);
+ iv.setSafe(3, 503);
+ vsr.setRowCount(4);
+ try (AdbcStatement.QueryResult result = stmt.executeQuery()) {
+ var values = ArrowToJava.toIntegers(result.getReader(), "baz");
+ assertThat(values).containsExactly(null, 402, 201, 504);
+ }
+ }
+ }
+
+ @Test
+ void bindUpdateQuery() throws Exception {
+ runSetup("DROP TABLE IF EXISTS foobar", "CREATE TABLE foobar (i INT, j
BIGINT)");
+
+ Schema schema =
+ new Schema(
+ List.of(
+ Field.nullable("$1", Types.MinorType.INT.getType()),
+ Field.nullable("$2", Types.MinorType.BIGINT.getType())));
+ try (var stmt = conn.createStatement();
+ VectorSchemaRoot vsr = VectorSchemaRoot.create(schema, allocator)) {
+ stmt.setSqlQuery("INSERT INTO foobar VALUES ($1, $2 * 2)");
+
+ var iv = (IntVector) vsr.getVector(0);
+ iv.setSafe(0, 1);
+ iv.setSafe(1, 42);
+ iv.setNull(2);
+
+ var biv = (BigIntVector) vsr.getVector(1);
+ biv.setSafe(0, 0);
+ biv.setSafe(1, 1);
+ biv.setSafe(2, 2);
+
+ vsr.setRowCount(3);
+ stmt.bind(vsr);
+
+ AdbcStatement.UpdateResult result = stmt.executeUpdate();
+ assertThat(result.getAffectedRows()).isEqualTo(3);
+
+ iv.setSafe(0, 100);
+ biv.setSafe(0, 3);
+ vsr.setRowCount(1);
+
+ result = stmt.executeUpdate();
+ assertThat(result.getAffectedRows()).isEqualTo(1);
+
+ stmt.setSqlQuery("SELECT i FROM foobar ORDER BY j ASC");
+ try (AdbcStatement.QueryResult queryResult = stmt.executeQuery()) {
+ var values = ArrowToJava.toIntegers(queryResult.getReader(), "i");
+ assertThat(values).containsExactly(1, 42, null, 100);
+ }
+ }
+ }
+
+ void runSetup(String... sql) throws Exception {
+ try (var stmt = conn.createStatement()) {
+ for (String s : sql) {
+ stmt.setSqlQuery(s);
+ stmt.executeUpdate();
+ }
+ }
+ }
+
+ void testOptions(AdbcOptions handle) {
+ AdbcException e;
+
+ e =
+ assertThrows(
+ AdbcException.class, () -> handle.getOption(new TypedKey<>("foo",
String.class)));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_FOUND);
+ e =
+ assertThrows(
+ AdbcException.class, () -> handle.getOption(new TypedKey<>("foo",
Integer.class)));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_FOUND);
+ e =
+ assertThrows(
+ AdbcException.class, () -> handle.getOption(new TypedKey<>("foo",
Long.class)));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_FOUND);
+ e =
+ assertThrows(
+ AdbcException.class, () -> handle.getOption(new TypedKey<>("foo",
Boolean.class)));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_FOUND);
+ e =
+ assertThrows(
+ AdbcException.class, () -> handle.getOption(new TypedKey<>("foo",
Float.class)));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_FOUND);
+ e =
+ assertThrows(
+ AdbcException.class, () -> handle.getOption(new TypedKey<>("foo",
Double.class)));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_FOUND);
+ e =
+ assertThrows(
+ AdbcException.class, () -> handle.getOption(new TypedKey<>("foo",
byte[].class)));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_FOUND);
+
+ e =
+ assertThrows(
+ AdbcException.class,
+ () -> handle.setOption(new TypedKey<>("foo", String.class),
"bar"));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_IMPLEMENTED);
+ e =
+ assertThrows(
+ AdbcException.class, () -> handle.setOption(new TypedKey<>("foo",
Integer.class), 42));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_IMPLEMENTED);
+ e =
+ assertThrows(
+ AdbcException.class, () -> handle.setOption(new TypedKey<>("foo",
Long.class), 42L));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_IMPLEMENTED);
+ e =
+ assertThrows(
+ AdbcException.class,
+ () -> handle.setOption(new TypedKey<>("foo", Boolean.class),
true));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_IMPLEMENTED);
+ e =
+ assertThrows(
+ AdbcException.class, () -> handle.setOption(new TypedKey<>("foo",
Float.class), 3.14f));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_IMPLEMENTED);
+ e =
+ assertThrows(
+ AdbcException.class, () -> handle.setOption(new TypedKey<>("foo",
Double.class), 3.14));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_IMPLEMENTED);
+ e =
+ assertThrows(
+ AdbcException.class,
+ () -> handle.setOption(new TypedKey<>("foo", byte[].class), new
byte[] {1, 2, 3}));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_IMPLEMENTED);
+ }
+}
diff --git
a/java/driver/jni-validation/src/test/java/org/apache/arrow/adbc/driver/jni/SqlServerIntegrationTest.java
b/java/driver/jni-validation/src/test/java/org/apache/arrow/adbc/driver/jni/SqlServerIntegrationTest.java
new file mode 100644
index 000000000..8a6737816
--- /dev/null
+++
b/java/driver/jni-validation/src/test/java/org/apache/arrow/adbc/driver/jni/SqlServerIntegrationTest.java
@@ -0,0 +1,527 @@
+/*
+ * 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 static
org.apache.arrow.adbc.driver.testsuite.ArrowAssertions.assertSchema;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.apache.arrow.adbc.core.AdbcConnection;
+import org.apache.arrow.adbc.core.AdbcDatabase;
+import org.apache.arrow.adbc.core.AdbcDriver;
+import org.apache.arrow.adbc.core.AdbcException;
+import org.apache.arrow.adbc.core.AdbcInfoCode;
+import org.apache.arrow.adbc.core.AdbcOptions;
+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.TypedKey;
+import org.apache.arrow.adbc.driver.testsuite.ArrowToJava;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.memory.RootAllocator;
+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.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;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assumptions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class SqlServerIntegrationTest {
+ public static final String URI_ENV = "ADBC_MSSQL_TEST_URI";
+ static String URI = System.getenv(URI_ENV);
+
+ BufferAllocator allocator;
+ JniDriver driver;
+ AdbcDatabase db;
+ AdbcConnection conn;
+
+ @BeforeAll
+ static void beforeAll() {
+ Assumptions.assumeFalse(
+ URI == null || URI.isEmpty(),
+ String.format("Must set %s to run MSSQL integration tests", URI_ENV));
+ }
+
+ @BeforeEach
+ void beforeEach() throws Exception {
+ System.err.println("Connecting to MSSQL with URI: " + URI);
+ allocator = new RootAllocator();
+ driver = new JniDriver(allocator);
+ Map<String, Object> parameters = new HashMap<>();
+ JniDriver.PARAM_DRIVER.set(parameters, "mssql");
+ AdbcDriver.PARAM_URI.set(parameters, URI);
+ db = driver.open(parameters);
+ conn = db.connect();
+ }
+
+ @AfterEach
+ void afterEach() throws Exception {
+ conn.close();
+ db.close();
+ allocator.close();
+ }
+
+ @Test
+ void simple() throws Exception {
+ try (var stmt = conn.createStatement()) {
+ stmt.setSqlQuery("SELECT 1 + 1 AS sum");
+ try (var reader = stmt.executeQuery()) {
+ assertThat(reader.getReader().loadNextBatch()).isTrue();
+
assertThat(reader.getReader().getVectorSchemaRoot().getVector("sum").getObject(0))
+ .isEqualTo(2);
+ }
+ }
+ }
+
+ @Test
+ void options() throws Exception {
+ testOptions(db);
+ testOptions(conn);
+ try (AdbcStatement stmt = conn.createStatement()) {
+ testOptions(stmt);
+ }
+ }
+
+ @Test
+ void connectionGetInfo() throws Exception {
+ try (ArrowReader reader = conn.getInfo()) {
+ assertThat(reader.loadNextBatch()).isTrue();
+
+ var codes =
ArrowToJava.toIntegers(reader.getVectorSchemaRoot().getVector("info_name"));
+ var values =
ArrowToJava.toObjects(reader.getVectorSchemaRoot().getVector("info_value"));
+ var infos =
+ IntStream.range(0, codes.size())
+ .mapToObj(i -> Map.entry(codes.get(i), values.get(i)))
+ .collect(Collectors.toMap(Map.Entry::getKey,
Map.Entry::getValue));
+
+ assertThat(codes).contains(AdbcInfoCode.VENDOR_NAME.getValue());
+ assertThat(codes).contains(AdbcInfoCode.VENDOR_VERSION.getValue());
+ assertThat(codes).contains(AdbcInfoCode.DRIVER_NAME.getValue());
+ assertThat(codes).contains(AdbcInfoCode.DRIVER_VERSION.getValue());
+
assertThat(infos.get(AdbcInfoCode.VENDOR_NAME.getValue())).isEqualTo("Microsoft
SQL Server");
+ assertThat(infos.get(AdbcInfoCode.DRIVER_NAME.getValue()))
+ .isEqualTo("Columnar ADBC Driver for Microsoft SQL Server");
+
+ assertThat(reader.loadNextBatch()).isFalse();
+ }
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ void connectionGetObjects() throws Exception {
+ runSetup(
+ "DROP TABLE IF EXISTS test_schema.foobar",
+ "DROP SCHEMA IF EXISTS test_schema",
+ "DROP TABLE IF EXISTS foobar",
+ "CREATE SCHEMA test_schema",
+ "CREATE TABLE test_schema.foobar (a INT)",
+ "CREATE TABLE foobar (b VARCHAR)");
+
+ try (var reader =
+ conn.getObjects(
+ AdbcConnection.GetObjectsDepth.ALL, null, "TEST_SCHEMA", null,
null, null)) {
+ var values =
+ ArrowToJava.toObjects(reader, "catalog_db_schemas").stream()
+ .filter(Objects::nonNull)
+ .filter(x -> !((List<?>) x).isEmpty())
+ .collect(Collectors.toList());
+ var schemas = (List<?>) values.get(0);
+ assertThat(schemas).isNotNull();
+ var schema = (Map<String, ?>) schemas.get(0);
+
assertThat(schema).extractingByKey("db_schema_name").isEqualTo("test_schema");
+ var tables = (List<?>) schema.get("db_schema_tables");
+ assertThat(tables).size().isEqualTo(1);
+ var table = (Map<String, ?>) tables.get(0);
+ assertThat(table).extractingByKey("table_name").isEqualTo("foobar");
+ assertThat(table).extractingByKey("table_type").isEqualTo("BASE TABLE");
+ var columns = (List<Map<String, ?>>) table.get("table_columns");
+
assertThat(columns).singleElement().extracting("column_name").isEqualTo("a");
+ }
+ }
+
+ @Test
+ void connectionGetTableSchema() throws Exception {
+ runSetup(
+ "DROP TABLE IF EXISTS test_schema.foobar",
+ "DROP SCHEMA IF EXISTS test_schema",
+ "DROP TABLE IF EXISTS foobar",
+ "CREATE SCHEMA test_schema",
+ "CREATE TABLE test_schema.foobar (a INT)",
+ "CREATE TABLE foobar (b VARCHAR)");
+
+ // XXX: this appears to get the wrong table
+ // assertSchema(conn.getTableSchema(null, "test_schema", "foobar"))
+ // .isEqualTo(new Schema(List.of(Field.nullable("a",
Types.MinorType.INT.getType()))));
+ assertSchema(conn.getTableSchema(null, null, "foobar"))
+ .isEqualTo(new Schema(List.of(Field.nullable("b",
Types.MinorType.VARCHAR.getType()))));
+ }
+
+ @Test
+ void connectionTableTypes() throws Exception {
+ try (ArrowReader reader = conn.getTableTypes()) {
+ List<String> tableTypes = ArrowToJava.toStrings(reader, "table_type");
+ assertThat(tableTypes).containsExactlyInAnyOrder("BASE TABLE", "VIEW");
+ }
+ }
+
+ @Test
+ void connectionReadOnly() {
+ AdbcException e = assertThrows(AdbcException.class, () ->
conn.getReadOnly());
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_FOUND);
+
+ e = assertThrows(AdbcException.class, () -> conn.setReadOnly(true));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.INVALID_ARGUMENT);
+ }
+
+ @Test
+ void bulkIngest() throws Exception {
+ runSetup("DROP TABLE IF EXISTS foobar");
+
+ final Schema schema =
+ new Schema(
+ List.of(
+ Field.nullable("ndx", Types.MinorType.INT.getType()),
+ Field.nullable("value", Types.MinorType.VARCHAR.getType())));
+ try (VectorSchemaRoot vsr = VectorSchemaRoot.create(schema, allocator)) {
+ IntVector iv = (IntVector) vsr.getVector(0);
+ VarCharVector vv = (VarCharVector) vsr.getVector(1);
+
+ try (AdbcStatement stmt = conn.bulkIngest("foobar",
BulkIngestMode.CREATE)) {
+ iv.setSafe(0, 1);
+ iv.setSafe(1, 2);
+ vv.setNull(0);
+ vv.setSafe(1, "foobar".getBytes(StandardCharsets.UTF_8));
+ vsr.setRowCount(2);
+
+ stmt.bind(vsr);
+ assertThat(stmt.executeUpdate().getAffectedRows()).isEqualTo(2);
+ }
+
+ try (AdbcStatement stmt = conn.bulkIngest("foobar",
BulkIngestMode.REPLACE)) {
+ iv.setSafe(0, 1);
+ iv.setSafe(1, 2);
+ vv.setSafe(0, "".getBytes(StandardCharsets.UTF_8));
+ vv.setSafe(1, "testtest".getBytes(StandardCharsets.UTF_8));
+ vsr.setRowCount(2);
+
+ stmt.bind(vsr);
+ assertThat(stmt.executeUpdate().getAffectedRows()).isEqualTo(2);
+ }
+
+ try (AdbcStatement stmt = conn.bulkIngest("foobar",
BulkIngestMode.APPEND)) {
+ iv.setSafe(0, 3);
+ iv.setSafe(1, 4);
+ iv.setSafe(2, 5);
+ vv.setSafe(0, "spam".getBytes(StandardCharsets.UTF_8));
+ vv.setNull(1);
+ vv.setSafe(2, "eggs".getBytes(StandardCharsets.UTF_8));
+ vsr.setRowCount(3);
+
+ stmt.bind(vsr);
+ assertThat(stmt.executeUpdate().getAffectedRows()).isEqualTo(3);
+ }
+
+ try (AdbcStatement stmt = conn.bulkIngest("foobar",
BulkIngestMode.CREATE_APPEND)) {
+ iv.setSafe(0, 6);
+ iv.setSafe(1, 7);
+ iv.setSafe(2, 8);
+ vv.setSafe(0, "spam".getBytes(StandardCharsets.UTF_8));
+ vv.setNull(1);
+ vv.setNull(2);
+ vsr.setRowCount(3);
+
+ stmt.bind(vsr);
+ assertThat(stmt.executeUpdate().getAffectedRows()).isEqualTo(3);
+ }
+ }
+
+ try (AdbcStatement stmt = conn.createStatement()) {
+ stmt.setSqlQuery("SELECT value FROM foobar ORDER BY ndx");
+ try (var result = stmt.executeQuery()) {
+ var values = ArrowToJava.toStrings(result.getReader(), "value");
+ assertThat(values)
+ .containsExactly("", "testtest", "spam", null, "eggs", "spam",
null, null);
+ }
+ }
+ }
+
+ @Test
+ void currentCatalogSchema() throws Exception {
+ runSetup(
+ "DROP TABLE IF EXISTS test_schema.foobar",
+ "DROP SCHEMA IF EXISTS test_schema",
+ "DROP TABLE IF EXISTS foobar",
+ "CREATE SCHEMA test_schema",
+ "CREATE TABLE test_schema.foobar (a INT)",
+ "CREATE TABLE foobar (b VARCHAR)");
+ assertThat(conn.getCurrentCatalog()).isEqualTo("master");
+ assertThat(conn.getCurrentDbSchema()).isEqualTo("dbo");
+
+ AdbcException e = assertThrows(AdbcException.class, () ->
conn.setCurrentCatalog("foobar"));
+ assertThat(e).hasMessageContaining("Database 'foobar' does not exist");
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.INTERNAL);
+
+ // MSSQL does not let you change the search path
+ e = assertThrows(AdbcException.class, () ->
conn.setCurrentDbSchema("test_schema"));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_IMPLEMENTED);
+
+ try (AdbcStatement stmt = conn.createStatement()) {
+ stmt.setSqlQuery("SELECT * FROM foobar");
+ try (AdbcStatement.QueryResult result = stmt.executeQuery()) {
+ assertSchema(result.getReader().getVectorSchemaRoot().getSchema())
+ .isEqualTo(new Schema(List.of(Field.nullable("b",
Types.MinorType.VARCHAR.getType()))));
+ }
+ }
+ }
+
+ @Test
+ void transactions() throws Exception {}
+
+ @Test
+ void selectQuery() throws Exception {
+ try (var stmt = conn.createStatement()) {
+ stmt.setSqlQuery("SELECT 42 AS THEANSWER, 'meaning of life' AS
THEQUESTION");
+
+ assertSchema(stmt.executeSchema())
+ .isEqualTo(
+ new Schema(
+ List.of(
+ Field.notNullable("THEANSWER",
Types.MinorType.INT.getType()),
+ Field.notNullable("THEQUESTION",
Types.MinorType.VARCHAR.getType()))));
+
+ try (AdbcStatement.QueryResult result = stmt.executeQuery()) {
+ assertThat(result.getReader().loadNextBatch()).isTrue();
+ assertThat(result.getReader().loadNextBatch()).isFalse();
+ }
+ }
+ // TODO(https://github.com/apache/arrow-adbc/issues/4239): test get
parameter schema
+ }
+
+ @Test
+ void updateQuery() throws Exception {
+ runSetup("DROP TABLE IF EXISTS foobar", "CREATE TABLE foobar (i INT)");
+
+ try (var stmt = conn.createStatement()) {
+ stmt.setSqlQuery("INSERT INTO foobar VALUES (1), (2)");
+ AdbcStatement.UpdateResult result = stmt.executeUpdate();
+ assertThat(result.getAffectedRows()).isEqualTo(2);
+ }
+ }
+
+ @Test
+ void prepare() throws Exception {
+ try (var stmt = conn.createStatement()) {
+ assertThat(assertThrows(AdbcException.class, stmt::prepare))
+ .hasMessageContaining("Cannot Prepare without a query");
+ }
+ }
+
+ @Test
+ void prepareSelectQuery() throws Exception {
+ runSetup(
+ "DROP TABLE IF EXISTS foobar",
+ "CREATE TABLE foobar (i INT)",
+ "INSERT INTO foobar VALUES (1), (2)");
+
+ try (var stmt = conn.createStatement()) {
+ stmt.setSqlQuery("SELECT i + 2 AS baz FROM foobar ORDER BY baz ASC");
+ stmt.prepare();
+
+ assertSchema(stmt.executeSchema())
+ .isEqualTo(new Schema(List.of(Field.nullable("baz",
Types.MinorType.INT.getType()))));
+
+ try (AdbcStatement.QueryResult result = stmt.executeQuery()) {
+ var values = ArrowToJava.toIntegers(result.getReader(), "baz");
+ assertThat(values).containsExactly(3, 4);
+ }
+
+ try (AdbcStatement.QueryResult result = stmt.executeQuery()) {
+ var values = ArrowToJava.toIntegers(result.getReader(), "baz");
+ assertThat(values).containsExactly(3, 4);
+ }
+ }
+ }
+
+ @Test
+ void prepareUpdateQuery() throws Exception {
+ runSetup("DROP TABLE IF EXISTS foobar", "CREATE TABLE foobar (i INT)");
+
+ try (var stmt = conn.createStatement()) {
+ stmt.setSqlQuery("INSERT INTO foobar VALUES (1), (2)");
+ stmt.prepare();
+
+ AdbcStatement.UpdateResult result = stmt.executeUpdate();
+ assertThat(result.getAffectedRows()).isEqualTo(2);
+
+ result = stmt.executeUpdate();
+ assertThat(result.getAffectedRows()).isEqualTo(2);
+ }
+ }
+
+ @Test
+ void bindSelectQuery() throws Exception {
+ Schema schema = new Schema(List.of(Field.nullable("$1",
Types.MinorType.INT.getType())));
+ try (var stmt = conn.createStatement();
+ VectorSchemaRoot vsr = VectorSchemaRoot.create(schema, allocator)) {
+ stmt.setSqlQuery("SELECT @p1 + 1 AS baz");
+ // XXX: not supported due to parameter
+ // assertSchema(stmt.executeSchema())
+ // .isEqualTo(new Schema(List.of(Field.nullable("baz",
Types.MinorType.INT.getType()))));
+
+ var iv = (IntVector) vsr.getVector(0);
+ iv.setSafe(0, 1);
+ iv.setSafe(1, 42);
+ iv.setNull(2);
+ vsr.setRowCount(3);
+ stmt.bind(vsr);
+
+ try (AdbcStatement.QueryResult result = stmt.executeQuery()) {
+ var values = ArrowToJava.toIntegers(result.getReader(), "baz");
+ assertThat(values).containsExactly(2, 43, null);
+ }
+
+ iv.setNull(0);
+ iv.setSafe(1, 401);
+ iv.setSafe(2, 200);
+ iv.setSafe(3, 503);
+ vsr.setRowCount(4);
+ try (AdbcStatement.QueryResult result = stmt.executeQuery()) {
+ var values = ArrowToJava.toIntegers(result.getReader(), "baz");
+ assertThat(values).containsExactly(null, 402, 201, 504);
+ }
+ }
+ }
+
+ @Test
+ void bindUpdateQuery() throws Exception {
+ runSetup("DROP TABLE IF EXISTS foobar", "CREATE TABLE foobar (i INT, j
BIGINT)");
+
+ Schema schema =
+ new Schema(
+ List.of(
+ Field.nullable("$1", Types.MinorType.INT.getType()),
+ Field.nullable("$2", Types.MinorType.BIGINT.getType())));
+ try (var stmt = conn.createStatement();
+ VectorSchemaRoot vsr = VectorSchemaRoot.create(schema, allocator)) {
+ stmt.setSqlQuery("INSERT INTO foobar VALUES (@p1, @p2 * 2)");
+
+ var iv = (IntVector) vsr.getVector(0);
+ iv.setSafe(0, 1);
+ iv.setSafe(1, 42);
+ iv.setNull(2);
+
+ var biv = (BigIntVector) vsr.getVector(1);
+ biv.setSafe(0, 0);
+ biv.setSafe(1, 1);
+ biv.setSafe(2, 2);
+
+ vsr.setRowCount(3);
+ stmt.bind(vsr);
+
+ // XXX: appears to be a leak in the driver
+ // AdbcStatement.UpdateResult result = stmt.executeUpdate();
+ // assertThat(result.getAffectedRows()).isEqualTo(3);
+
+ // iv.setSafe(0, 100);
+ // biv.setSafe(0, 3);
+ // vsr.setRowCount(1);
+
+ // result = stmt.executeUpdate();
+ // assertThat(result.getAffectedRows()).isEqualTo(1);
+
+ // stmt.setSqlQuery("SELECT i FROM foobar ORDER BY j ASC");
+ // try (AdbcStatement.QueryResult queryResult = stmt.executeQuery()) {
+ // var values = ArrowToJava.toIntegers(queryResult.getReader(), "i");
+ // assertThat(values).containsExactly(1, 42, null, 100);
+ // }
+ }
+ }
+
+ void runSetup(String... sql) throws Exception {
+ try (var stmt = conn.createStatement()) {
+ for (String s : sql) {
+ stmt.setSqlQuery(s);
+ stmt.executeUpdate();
+ }
+ }
+ }
+
+ void testOptions(AdbcOptions handle) {
+ AdbcException e;
+
+ e =
+ assertThrows(
+ AdbcException.class, () -> handle.getOption(new TypedKey<>("foo",
String.class)));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_FOUND);
+ e =
+ assertThrows(
+ AdbcException.class, () -> handle.getOption(new TypedKey<>("foo",
Integer.class)));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_FOUND);
+ e =
+ assertThrows(
+ AdbcException.class, () -> handle.getOption(new TypedKey<>("foo",
Long.class)));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_FOUND);
+ e =
+ assertThrows(
+ AdbcException.class, () -> handle.getOption(new TypedKey<>("foo",
Boolean.class)));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_FOUND);
+ e =
+ assertThrows(
+ AdbcException.class, () -> handle.getOption(new TypedKey<>("foo",
Float.class)));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_FOUND);
+ e =
+ assertThrows(
+ AdbcException.class, () -> handle.getOption(new TypedKey<>("foo",
Double.class)));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_FOUND);
+ e =
+ assertThrows(
+ AdbcException.class, () -> handle.getOption(new TypedKey<>("foo",
byte[].class)));
+ assertThat(e.getStatus()).isEqualTo(AdbcStatusCode.NOT_FOUND);
+
+ // XXX: the returned status code is inconsistent between different handle
types
+ assertThrows(
+ AdbcException.class, () -> handle.setOption(new TypedKey<>("foo",
String.class), "bar"));
+ assertThrows(
+ AdbcException.class, () -> handle.setOption(new TypedKey<>("foo",
Integer.class), 42));
+ assertThrows(
+ AdbcException.class, () -> handle.setOption(new TypedKey<>("foo",
Long.class), 42L));
+ assertThrows(
+ AdbcException.class, () -> handle.setOption(new TypedKey<>("foo",
Boolean.class), true));
+ assertThrows(
+ AdbcException.class, () -> handle.setOption(new TypedKey<>("foo",
Float.class), 3.14f));
+ assertThrows(
+ AdbcException.class, () -> handle.setOption(new TypedKey<>("foo",
Double.class), 3.14));
+ assertThrows(
+ AdbcException.class,
+ () -> handle.setOption(new TypedKey<>("foo", byte[].class), new byte[]
{1, 2, 3}));
+ }
+}
diff --git
a/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/AbstractConnectionTest.java
b/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/AbstractConnectionTest.java
index 54e605904..aa7a247bd 100644
---
a/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/AbstractConnectionTest.java
+++
b/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/AbstractConnectionTest.java
@@ -50,7 +50,7 @@ public abstract class AbstractConnectionTest {
}
@Test
- void currentCatalog() throws Exception {
+ protected void currentCatalog() throws Exception {
assumeThat(quirks.supportsCurrentCatalog()).isTrue();
assertThat(connection.getCurrentCatalog()).isEqualTo(quirks.defaultCatalog());
@@ -63,17 +63,17 @@ public abstract class AbstractConnectionTest {
}
@Test
- void multipleConnections() throws Exception {
+ protected void multipleConnections() throws Exception {
try (final AdbcConnection ignored = database.connect()) {}
}
@Test
- void readOnly() throws Exception {
+ protected void readOnly() throws Exception {
assertThat(connection.getReadOnly()).isFalse();
}
@Test
- void isolationLevel() throws Exception {
+ protected void isolationLevel() throws Exception {
connection.getIsolationLevel();
}
}
diff --git
a/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/ArrowToJava.java
b/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/ArrowToJava.java
new file mode 100644
index 000000000..243a5199c
--- /dev/null
+++
b/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/ArrowToJava.java
@@ -0,0 +1,287 @@
+/*
+ * 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.testsuite;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.arrow.vector.BigIntVector;
+import org.apache.arrow.vector.BitVector;
+import org.apache.arrow.vector.IntVector;
+import org.apache.arrow.vector.LargeVarCharVector;
+import org.apache.arrow.vector.SmallIntVector;
+import org.apache.arrow.vector.UInt4Vector;
+import org.apache.arrow.vector.ValueVector;
+import org.apache.arrow.vector.VarCharVector;
+import org.apache.arrow.vector.complex.DenseUnionVector;
+import org.apache.arrow.vector.complex.ListVector;
+import org.apache.arrow.vector.complex.StructVector;
+import org.apache.arrow.vector.ipc.ArrowReader;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public final class ArrowToJava {
+ private ArrowToJava() {
+ throw new AssertionError();
+ }
+
+ public static List<@Nullable Long> toLongs(List<@Nullable Long> result,
ValueVector vector) {
+ if (vector instanceof IntVector) {
+ IntVector v = (IntVector) vector;
+ for (int i = 0; i < v.getValueCount(); i++) {
+ if (v.isNull(i)) {
+ result.add(null);
+ } else {
+ result.add((long) v.get(i));
+ }
+ }
+ return result;
+ } else if (vector instanceof BigIntVector) {
+ BigIntVector v = (BigIntVector) vector;
+ for (int i = 0; i < v.getValueCount(); i++) {
+ if (v.isNull(i)) {
+ result.add(null);
+ } else {
+ result.add(v.get(i));
+ }
+ }
+ return result;
+ } else if (vector instanceof UInt4Vector) {
+ UInt4Vector v = (UInt4Vector) vector;
+ for (int i = 0; i < v.getValueCount(); i++) {
+ if (v.isNull(i)) {
+ result.add(null);
+ } else {
+ result.add(Integer.toUnsignedLong(v.get(i)));
+ }
+ }
+ return result;
+ } else {
+ throw new IllegalArgumentException("Unsupported vector type: " +
vector.getClass());
+ }
+ }
+
+ public static List<@Nullable Long> toLongs(ValueVector vector) {
+ return toLongs(new ArrayList<>(), vector);
+ }
+
+ public static List<@Nullable Long> toLongs(ArrowReader reader, String
fieldName)
+ throws IOException {
+ return collect(reader, fieldName, ArrowToJava::toLongs);
+ }
+
+ public static List<@Nullable Integer> toIntegers(
+ List<@Nullable Integer> result, ValueVector vector) {
+ if (vector instanceof IntVector) {
+ IntVector v = (IntVector) vector;
+ for (int i = 0; i < v.getValueCount(); i++) {
+ if (v.isNull(i)) {
+ result.add(null);
+ } else {
+ result.add(v.get(i));
+ }
+ }
+ return result;
+ } else if (vector instanceof UInt4Vector) {
+ UInt4Vector v = (UInt4Vector) vector;
+ for (int i = 0; i < v.getValueCount(); i++) {
+ if (v.isNull(i)) {
+ result.add(null);
+ } else {
+ result.add(v.get(i));
+ }
+ }
+ return result;
+ } else {
+ throw new IllegalArgumentException("Unsupported vector type: " +
vector.getClass());
+ }
+ }
+
+ public static List<@Nullable Integer> toIntegers(ValueVector vector) {
+ return toIntegers(new ArrayList<>(), vector);
+ }
+
+ public static List<@Nullable Integer> toIntegers(ArrowReader reader, String
fieldName)
+ throws IOException {
+ return collect(reader, fieldName, ArrowToJava::toIntegers);
+ }
+
+ public static List<@Nullable String> toStrings(
+ List<@Nullable String> result, ValueVector vector) {
+ if (vector instanceof VarCharVector) {
+ VarCharVector v = (VarCharVector) vector;
+ for (int i = 0; i < v.getValueCount(); i++) {
+ if (v.isNull(i)) {
+ result.add(null);
+ } else {
+ result.add(v.getObject(i).toString());
+ }
+ }
+ } else if (vector instanceof LargeVarCharVector) {
+ LargeVarCharVector v = (LargeVarCharVector) vector;
+ for (int i = 0; i < v.getValueCount(); i++) {
+ if (v.isNull(i)) {
+ result.add(null);
+ } else {
+ result.add(v.getObject(i).toString());
+ }
+ }
+ } else {
+ throw new IllegalArgumentException("Unsupported vector type: " +
vector.getClass());
+ }
+ return result;
+ }
+
+ public static List<@Nullable String> toStrings(ValueVector vector) {
+ return toStrings(new ArrayList<>(), vector);
+ }
+
+ public static List<@Nullable String> toStrings(ArrowReader reader, String
fieldName)
+ throws IOException {
+ return collect(reader, fieldName, ArrowToJava::toStrings);
+ }
+
+ public static List<@Nullable Object> toObjects(
+ List<@Nullable Object> result, ValueVector vector) {
+ for (int i = 0; i < vector.getValueCount(); i++) {
+ result.add(getObject(vector, i));
+ }
+ return result;
+ }
+
+ public static List<@Nullable Object> toObjects(ValueVector vector) {
+ return toObjects(new ArrayList<>(), vector);
+ }
+
+ public static List<@Nullable Object> toObjects(ArrowReader reader, String
fieldName)
+ throws IOException {
+ return collect(reader, fieldName, ArrowToJava::toObjects);
+ }
+
+ static <T> List<@Nullable T> collect(
+ ArrowReader reader, String fieldName, ValueVectorCollector<T> collector)
throws IOException {
+ List<@Nullable T> result = new ArrayList<>();
+ while (reader.loadNextBatch()) {
+ collector.collect(result,
reader.getVectorSchemaRoot().getVector(fieldName));
+ }
+ return result;
+ }
+
+ @FunctionalInterface
+ interface ValueVectorCollector<T> {
+ void collect(List<@Nullable T> result, ValueVector vector);
+ }
+
+ static @Nullable Object getObject(BigIntVector vector, int index) {
+ if (vector.isNull(index)) {
+ return null;
+ } else {
+ return vector.getObject(index);
+ }
+ }
+
+ static @Nullable Object getObject(BitVector vector, int index) {
+ if (vector.isNull(index)) {
+ return null;
+ } else {
+ return vector.getObject(index);
+ }
+ }
+
+ static @Nullable Object getObject(DenseUnionVector vector, int index) {
+ byte typeId = vector.getTypeId(index);
+ int offset = vector.getOffset(index);
+ return getObject(vector.getVectorByType(typeId), offset);
+ }
+
+ static @Nullable Object getObject(IntVector vector, int index) {
+ if (vector.isNull(index)) {
+ return null;
+ } else {
+ return vector.getObject(index);
+ }
+ }
+
+ static @Nullable Object getObject(ListVector vector, int index) {
+ if (vector.isNull(index)) {
+ return null;
+ } else {
+ List<@Nullable Object> vals = new ArrayList<>();
+ ValueVector children = vector.getDataVector();
+ int start = vector.getOffsetBuffer().getInt(index * 4L);
+ int end = vector.getOffsetBuffer().getInt((index + 1) * 4L);
+ for (int i = start; i < end; ++i) {
+ vals.add(getObject(children, i));
+ }
+ return vals;
+ }
+ }
+
+ static @Nullable Object getObject(SmallIntVector vector, int index) {
+ if (vector.isNull(index)) {
+ return null;
+ } else {
+ return vector.getObject(index);
+ }
+ }
+
+ static @Nullable Object getObject(StructVector vector, int index) {
+ if (vector.isNull(index)) {
+ return null;
+ } else {
+ Map<String, @Nullable Object> m = new HashMap<>();
+ for (String field : vector.getChildFieldNames()) {
+ var child = vector.getChild(field);
+ Object childValue = getObject(child, index);
+ m.put(field, childValue);
+ }
+ return m;
+ }
+ }
+
+ static @Nullable Object getObject(VarCharVector vector, int index) {
+ if (vector.isNull(index)) {
+ return null;
+ } else {
+ return vector.getObject(index).toString();
+ }
+ }
+
+ static @Nullable Object getObject(ValueVector vector, int index) {
+ if (vector instanceof BigIntVector) {
+ return getObject((BigIntVector) vector, index);
+ } else if (vector instanceof BitVector) {
+ return getObject((BitVector) vector, index);
+ } else if (vector instanceof DenseUnionVector) {
+ return getObject((DenseUnionVector) vector, index);
+ } else if (vector instanceof IntVector) {
+ return getObject((IntVector) vector, index);
+ } else if (vector instanceof ListVector) {
+ return getObject((ListVector) vector, index);
+ } else if (vector instanceof SmallIntVector) {
+ return getObject((SmallIntVector) vector, index);
+ } else if (vector instanceof StructVector) {
+ return getObject((StructVector) vector, index);
+ } else if (vector instanceof VarCharVector) {
+ return getObject((VarCharVector) vector, index);
+ } else {
+ throw new UnsupportedOperationException("Unsupported vector type: " +
vector.getClass());
+ }
+ }
+}
diff --git a/java/pom.xml b/java/pom.xml
index d83273080..fdbb08b56 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -333,7 +333,7 @@
<id>jni</id>
<modules>
<module>driver/jni</module>
- <module>driver/jni-validation-sqlite</module>
+ <module>driver/jni-validation</module>
</modules>
</profile>
</profiles>