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 1e1a5762 test(java): add Checker Framework annotations (#1495)
1e1a5762 is described below
commit 1e1a57623761d564ac4a99a213fbeb4142b4b57a
Author: David Li <[email protected]>
AuthorDate: Fri Feb 9 10:33:37 2024 -0500
test(java): add Checker Framework annotations (#1495)
Fixes #624.
---
.pre-commit-config.yaml | 2 +-
...pache.arrow.adapter.jdbc.JdbcToArrowUtils.astub | 28 ++++
.../org.apache.arrow.util.AutoCloseables.astub | 24 +++
....apache.arrow.vector.types.pojo.ArrowType.astub | 31 ++++
.../org.junit.jupiter.api.Assumptions.astub | 29 ++++
java/core/pom.xml | 6 +
.../org/apache/arrow/adbc/core/AdbcConnection.java | 3 +-
.../org/apache/arrow/adbc/core/AdbcDriver.java | 1 +
.../org/apache/arrow/adbc/core/AdbcException.java | 17 +-
.../org/apache/arrow/adbc/core/ErrorDetail.java | 3 +-
.../arrow/adbc/core/PartitionDescriptor.java | 3 +-
.../apache/arrow/adbc/core/StandardSchemas.java | 96 +++++++++---
.../java/org/apache/arrow/adbc/core/TypedKey.java | 10 +-
java/driver-manager/pom.xml | 6 +
.../adbc/drivermanager/AdbcDriverManager.java | 14 +-
.../adbc/driver/flightsql/FlightSqlQuirks.java | 4 +-
java/driver/flight-sql/pom.xml | 6 +
.../adbc/driver/flightsql/FlightInfoReader.java | 12 +-
.../adbc/driver/flightsql/FlightSqlConnection.java | 82 ++++++----
.../adbc/driver/flightsql/FlightSqlDriverUtil.java | 7 +-
.../adbc/driver/flightsql/FlightSqlStatement.java | 59 ++++---
.../adbc/driver/flightsql/InfoMetadataBuilder.java | 14 +-
.../driver/jdbc/postgresql/PostgreSqlTypeTest.java | 8 +-
java/driver/jdbc/pom.xml | 6 +
.../adbc/driver/jdbc/InfoMetadataBuilder.java | 22 ++-
.../arrow/adbc/driver/jdbc/JdbcArrowReader.java | 5 +-
.../arrow/adbc/driver/jdbc/JdbcBindReader.java | 15 +-
.../arrow/adbc/driver/jdbc/JdbcConnection.java | 40 +++--
.../adbc/driver/jdbc/JdbcDataSourceDatabase.java | 20 +--
.../apache/arrow/adbc/driver/jdbc/JdbcDriver.java | 7 +-
.../arrow/adbc/driver/jdbc/JdbcDriverUtil.java | 17 +-
.../apache/arrow/adbc/driver/jdbc/JdbcQuirks.java | 5 +-
.../arrow/adbc/driver/jdbc/JdbcStatement.java | 78 +++++++---
.../adbc/driver/jdbc/ObjectMetadataBuilder.java | 172 +++++++++++++++------
.../arrow/adbc/driver/jdbc/StandardJdbcQuirks.java | 47 +-----
.../arrow/adbc/driver/jdbc/UrlDataSource.java | 7 +-
.../driver/jdbc/adapter/JdbcFieldInfoExtra.java | 15 +-
.../jdbc/adapter/JdbcToArrowTypeConverters.java | 17 +-
.../testsuite/AbstractConnectionMetadataTest.java | 3 +-
.../driver/testsuite/AbstractTransactionTest.java | 12 +-
.../arrow/adbc/driver/testsuite/SqlTestUtil.java | 6 +-
java/pom.xml | 32 +++-
java/sql/pom.xml | 6 +
.../java/org/apache/arrow/adbc/sql/SqlQuirks.java | 79 +++++-----
44 files changed, 750 insertions(+), 326 deletions(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 480ca074..fb3a19cd 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -65,7 +65,7 @@ repos:
entry: bash -c 'cd go/adbc && golangci-lint run --fix --timeout 5m'
types_or: [go]
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
- rev: v2.3.0
+ rev: v2.12.0
hooks:
- id: pretty-format-golang
- id: pretty-format-java
diff --git
a/java/.checker-framework/org.apache.arrow.adapter.jdbc.JdbcToArrowUtils.astub
b/java/.checker-framework/org.apache.arrow.adapter.jdbc.JdbcToArrowUtils.astub
new file mode 100644
index 00000000..0ee9d0eb
--- /dev/null
+++
b/java/.checker-framework/org.apache.arrow.adapter.jdbc.JdbcToArrowUtils.astub
@@ -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.adapter.jdbc;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.Calendar;
+
+import org.apache.arrow.vector.types.pojo.ArrowType;
+
+public class JdbcToArrowUtils {
+ public static ArrowType getArrowTypeFromJdbcType(JdbcFieldInfo fieldInfo,
@Nullable Calendar calendar);
+}
diff --git a/java/.checker-framework/org.apache.arrow.util.AutoCloseables.astub
b/java/.checker-framework/org.apache.arrow.util.AutoCloseables.astub
new file mode 100644
index 00000000..f095df93
--- /dev/null
+++ b/java/.checker-framework/org.apache.arrow.util.AutoCloseables.astub
@@ -0,0 +1,24 @@
+/*
+ * 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.util;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+public final class AutoCloseables {
+ public static void close(@Nullable AutoCloseable... autoCloseables) throws
Exception;
+}
diff --git
a/java/.checker-framework/org.apache.arrow.vector.types.pojo.ArrowType.astub
b/java/.checker-framework/org.apache.arrow.vector.types.pojo.ArrowType.astub
new file mode 100644
index 00000000..8827b847
--- /dev/null
+++ b/java/.checker-framework/org.apache.arrow.vector.types.pojo.ArrowType.astub
@@ -0,0 +1,31 @@
+/*
+ * 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.vector.types.pojo;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import org.apache.arrow.vector.types.TimeUnit;
+
+public abstract class ArrowType {
+ public abstract static class PrimitiveType extends ArrowType {
+ }
+
+ public static class Timestamp extends PrimitiveType {
+ public Timestamp(TimeUnit unit, @Nullable String timezone);
+ }
+}
diff --git a/java/.checker-framework/org.junit.jupiter.api.Assumptions.astub
b/java/.checker-framework/org.junit.jupiter.api.Assumptions.astub
new file mode 100644
index 00000000..46ee85fb
--- /dev/null
+++ b/java/.checker-framework/org.junit.jupiter.api.Assumptions.astub
@@ -0,0 +1,29 @@
+/*
+ * 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.junit.jupiter.api;
+
+import org.checkerframework.dataflow.qual.AssertMethod;
+import org.opentest4j.TestAbortedException;
+
+public class Assumptions {
+ @AssertMethod(value = TestAbortedException.class)
+ public static void assumeTrue(boolean assumption, String message) throws
TestAbortedException;
+
+ @AssertMethod(isAssertFalse = true, value = TestAbortedException.class)
+ public static void assumeFalse(boolean assumption, String message) throws
TestAbortedException;
+}
diff --git a/java/core/pom.xml b/java/core/pom.xml
index ac499b9a..f61c6868 100644
--- a/java/core/pom.xml
+++ b/java/core/pom.xml
@@ -33,6 +33,12 @@
<artifactId>arrow-vector</artifactId>
</dependency>
+ <!-- Static analysis and linting -->
+ <dependency>
+ <groupId>org.checkerframework</groupId>
+ <artifactId>checker-qual</artifactId>
+ </dependency>
+
<!-- Testing -->
<dependency>
<groupId>org.assertj</groupId>
diff --git
a/java/core/src/main/java/org/apache/arrow/adbc/core/AdbcConnection.java
b/java/core/src/main/java/org/apache/arrow/adbc/core/AdbcConnection.java
index c8e897ee..060c65e8 100644
--- a/java/core/src/main/java/org/apache/arrow/adbc/core/AdbcConnection.java
+++ b/java/core/src/main/java/org/apache/arrow/adbc/core/AdbcConnection.java
@@ -20,6 +20,7 @@ import java.nio.ByteBuffer;
import org.apache.arrow.vector.VectorSchemaRoot;
import org.apache.arrow.vector.ipc.ArrowReader;
import org.apache.arrow.vector.types.pojo.Schema;
+import org.checkerframework.checker.nullness.qual.Nullable;
/**
* A connection to a {@link AdbcDatabase}.
@@ -79,7 +80,7 @@ public interface AdbcConnection extends AutoCloseable,
AdbcOptions {
*
* @param infoCodes The metadata items to fetch.
*/
- ArrowReader getInfo(int[] infoCodes) throws AdbcException;
+ ArrowReader getInfo(int @Nullable [] infoCodes) throws AdbcException;
/**
* Get metadata about the driver/database.
diff --git a/java/core/src/main/java/org/apache/arrow/adbc/core/AdbcDriver.java
b/java/core/src/main/java/org/apache/arrow/adbc/core/AdbcDriver.java
index 5e32fd1e..797b8635 100644
--- a/java/core/src/main/java/org/apache/arrow/adbc/core/AdbcDriver.java
+++ b/java/core/src/main/java/org/apache/arrow/adbc/core/AdbcDriver.java
@@ -54,6 +54,7 @@ public interface AdbcDriver {
/** ADBC API revision 1.0.0. */
long ADBC_VERSION_1_0_0 = 1_000_000;
+
/** ADBC API revision 1.1.0. */
long ADBC_VERSION_1_1_0 = 1_001_000;
diff --git
a/java/core/src/main/java/org/apache/arrow/adbc/core/AdbcException.java
b/java/core/src/main/java/org/apache/arrow/adbc/core/AdbcException.java
index dce7570e..193bbaa9 100644
--- a/java/core/src/main/java/org/apache/arrow/adbc/core/AdbcException.java
+++ b/java/core/src/main/java/org/apache/arrow/adbc/core/AdbcException.java
@@ -18,6 +18,7 @@ package org.apache.arrow.adbc.core;
import java.util.Collection;
import java.util.Collections;
+import org.checkerframework.checker.nullness.qual.Nullable;
/**
* An error in the database or ADBC driver.
@@ -34,20 +35,24 @@ import java.util.Collections;
*/
public class AdbcException extends Exception {
private final AdbcStatusCode status;
- private final String sqlState;
+ private final @Nullable String sqlState;
private final int vendorCode;
private Collection<ErrorDetail> details;
public AdbcException(
- String message, Throwable cause, AdbcStatusCode status, String sqlState,
int vendorCode) {
+ @Nullable String message,
+ @Nullable Throwable cause,
+ AdbcStatusCode status,
+ @Nullable String sqlState,
+ int vendorCode) {
this(message, cause, status, sqlState, vendorCode,
Collections.emptyList());
}
public AdbcException(
- String message,
- Throwable cause,
+ @Nullable String message,
+ @Nullable Throwable cause,
AdbcStatusCode status,
- String sqlState,
+ @Nullable String sqlState,
int vendorCode,
Collection<ErrorDetail> details) {
super(message, cause);
@@ -83,7 +88,7 @@ public class AdbcException extends Exception {
}
/** A SQLSTATE error code, if provided, as defined by the SQL:2003 standard.
*/
- public String getSqlState() {
+ public @Nullable String getSqlState() {
return sqlState;
}
diff --git
a/java/core/src/main/java/org/apache/arrow/adbc/core/ErrorDetail.java
b/java/core/src/main/java/org/apache/arrow/adbc/core/ErrorDetail.java
index 13521fb8..5149b9de 100644
--- a/java/core/src/main/java/org/apache/arrow/adbc/core/ErrorDetail.java
+++ b/java/core/src/main/java/org/apache/arrow/adbc/core/ErrorDetail.java
@@ -17,6 +17,7 @@
package org.apache.arrow.adbc.core;
import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
/** Additional details (not necessarily human-readable) contained in an {@link
AdbcException}. */
public class ErrorDetail {
@@ -37,7 +38,7 @@ public class ErrorDetail {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
diff --git
a/java/core/src/main/java/org/apache/arrow/adbc/core/PartitionDescriptor.java
b/java/core/src/main/java/org/apache/arrow/adbc/core/PartitionDescriptor.java
index 3f204780..5890db90 100644
---
a/java/core/src/main/java/org/apache/arrow/adbc/core/PartitionDescriptor.java
+++
b/java/core/src/main/java/org/apache/arrow/adbc/core/PartitionDescriptor.java
@@ -18,6 +18,7 @@ package org.apache.arrow.adbc.core;
import java.nio.ByteBuffer;
import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.Nullable;
/** An opaque descriptor for a part of a potentially distributed or
partitioned result set. */
public final class PartitionDescriptor {
@@ -32,7 +33,7 @@ public final class PartitionDescriptor {
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
diff --git
a/java/core/src/main/java/org/apache/arrow/adbc/core/StandardSchemas.java
b/java/core/src/main/java/org/apache/arrow/adbc/core/StandardSchemas.java
index c059bb1b..31f8ddb9 100644
--- a/java/core/src/main/java/org/apache/arrow/adbc/core/StandardSchemas.java
+++ b/java/core/src/main/java/org/apache/arrow/adbc/core/StandardSchemas.java
@@ -98,30 +98,64 @@ public final class StandardSchemas {
public static final List<Field> COLUMN_SCHEMA =
Arrays.asList(
- new Field("column_name",
FieldType.notNullable(ArrowType.Utf8.INSTANCE), null),
- new Field("ordinal_position", FieldType.nullable(INT32), null),
- new Field("remarks", FieldType.nullable(ArrowType.Utf8.INSTANCE),
null),
- new Field("xdbc_data_type", FieldType.nullable(INT16), null),
- new Field("xdbc_type_name",
FieldType.nullable(ArrowType.Utf8.INSTANCE), null),
- new Field("xdbc_column_size", FieldType.nullable(INT32), null),
- new Field("xdbc_decimal_digits", FieldType.nullable(INT16), null),
- new Field("xdbc_num_prec_radix", FieldType.nullable(INT16), null),
- new Field("xdbc_nullable", FieldType.nullable(INT16), null),
- new Field("xdbc_column_def",
FieldType.nullable(ArrowType.Utf8.INSTANCE), null),
- new Field("xdbc_sql_data_type", FieldType.nullable(INT16), null),
- new Field("xdbc_datetime_sub", FieldType.nullable(INT16), null),
- new Field("xdbc_char_octet_length", FieldType.nullable(INT32), null),
- new Field("xdbc_is_nullable",
FieldType.nullable(ArrowType.Utf8.INSTANCE), null),
- new Field("xdbc_scope_catalog",
FieldType.nullable(ArrowType.Utf8.INSTANCE), null),
- new Field("xdbc_scope_schema",
FieldType.nullable(ArrowType.Utf8.INSTANCE), null),
- new Field("xdbc_scope_table",
FieldType.nullable(ArrowType.Utf8.INSTANCE), null),
- new Field("xdbc_is_autoincrement",
FieldType.nullable(ArrowType.Bool.INSTANCE), null),
- new Field("xdbc_is_generatedcolumn",
FieldType.nullable(ArrowType.Bool.INSTANCE), null));
+ new Field(
+ "column_name",
+ FieldType.notNullable(ArrowType.Utf8.INSTANCE),
+ Collections.emptyList()),
+ new Field("ordinal_position", FieldType.nullable(INT32),
Collections.emptyList()),
+ new Field(
+ "remarks", FieldType.nullable(ArrowType.Utf8.INSTANCE),
Collections.emptyList()),
+ new Field("xdbc_data_type", FieldType.nullable(INT16),
Collections.emptyList()),
+ new Field(
+ "xdbc_type_name",
+ FieldType.nullable(ArrowType.Utf8.INSTANCE),
+ Collections.emptyList()),
+ new Field("xdbc_column_size", FieldType.nullable(INT32),
Collections.emptyList()),
+ new Field("xdbc_decimal_digits", FieldType.nullable(INT16),
Collections.emptyList()),
+ new Field("xdbc_num_prec_radix", FieldType.nullable(INT16),
Collections.emptyList()),
+ new Field("xdbc_nullable", FieldType.nullable(INT16),
Collections.emptyList()),
+ new Field(
+ "xdbc_column_def",
+ FieldType.nullable(ArrowType.Utf8.INSTANCE),
+ Collections.emptyList()),
+ new Field("xdbc_sql_data_type", FieldType.nullable(INT16),
Collections.emptyList()),
+ new Field("xdbc_datetime_sub", FieldType.nullable(INT16),
Collections.emptyList()),
+ new Field("xdbc_char_octet_length", FieldType.nullable(INT32),
Collections.emptyList()),
+ new Field(
+ "xdbc_is_nullable",
+ FieldType.nullable(ArrowType.Utf8.INSTANCE),
+ Collections.emptyList()),
+ new Field(
+ "xdbc_scope_catalog",
+ FieldType.nullable(ArrowType.Utf8.INSTANCE),
+ Collections.emptyList()),
+ new Field(
+ "xdbc_scope_schema",
+ FieldType.nullable(ArrowType.Utf8.INSTANCE),
+ Collections.emptyList()),
+ new Field(
+ "xdbc_scope_table",
+ FieldType.nullable(ArrowType.Utf8.INSTANCE),
+ Collections.emptyList()),
+ new Field(
+ "xdbc_is_autoincrement",
+ FieldType.nullable(ArrowType.Bool.INSTANCE),
+ Collections.emptyList()),
+ new Field(
+ "xdbc_is_generatedcolumn",
+ FieldType.nullable(ArrowType.Bool.INSTANCE),
+ Collections.emptyList()));
public static final List<Field> TABLE_SCHEMA =
Arrays.asList(
- new Field("table_name",
FieldType.notNullable(ArrowType.Utf8.INSTANCE), null),
- new Field("table_type",
FieldType.notNullable(ArrowType.Utf8.INSTANCE), null),
+ new Field(
+ "table_name",
+ FieldType.notNullable(ArrowType.Utf8.INSTANCE),
+ Collections.emptyList()),
+ new Field(
+ "table_type",
+ FieldType.notNullable(ArrowType.Utf8.INSTANCE),
+ Collections.emptyList()),
new Field(
"table_columns",
FieldType.nullable(ArrowType.List.INSTANCE),
@@ -136,7 +170,10 @@ public final class StandardSchemas {
public static final List<Field> DB_SCHEMA_SCHEMA =
Arrays.asList(
- new Field("db_schema_name",
FieldType.notNullable(ArrowType.Utf8.INSTANCE), null),
+ new Field(
+ "db_schema_name",
+ FieldType.notNullable(ArrowType.Utf8.INSTANCE),
+ Collections.emptyList()),
new Field(
"db_schema_tables",
FieldType.nullable(ArrowType.List.INSTANCE),
@@ -150,7 +187,10 @@ public final class StandardSchemas {
public static final Schema GET_OBJECTS_SCHEMA =
new Schema(
Arrays.asList(
- new Field("catalog_name",
FieldType.notNullable(ArrowType.Utf8.INSTANCE), null),
+ new Field(
+ "catalog_name",
+ FieldType.notNullable(ArrowType.Utf8.INSTANCE),
+ Collections.emptyList()),
new Field(
"catalog_db_schemas",
FieldType.nullable(ArrowType.List.INSTANCE),
@@ -180,7 +220,10 @@ public final class StandardSchemas {
public static final List<Field> STATISTICS_DB_SCHEMA_SCHEMA =
Arrays.asList(
- new Field("db_schema_name",
FieldType.notNullable(ArrowType.Utf8.INSTANCE), null),
+ new Field(
+ "db_schema_name",
+ FieldType.notNullable(ArrowType.Utf8.INSTANCE),
+ Collections.emptyList()),
new Field(
"db_schema_statistics",
FieldType.notNullable(ArrowType.List.INSTANCE),
@@ -195,7 +238,10 @@ public final class StandardSchemas {
public static final Schema GET_STATISTICS_SCHEMA =
new Schema(
Arrays.asList(
- new Field("catalog_name",
FieldType.notNullable(ArrowType.Utf8.INSTANCE), null),
+ new Field(
+ "catalog_name",
+ FieldType.notNullable(ArrowType.Utf8.INSTANCE),
+ Collections.emptyList()),
new Field(
"catalog_db_schemas",
FieldType.notNullable(ArrowType.List.INSTANCE),
diff --git a/java/core/src/main/java/org/apache/arrow/adbc/core/TypedKey.java
b/java/core/src/main/java/org/apache/arrow/adbc/core/TypedKey.java
index 21523bb4..1f1dda2f 100644
--- a/java/core/src/main/java/org/apache/arrow/adbc/core/TypedKey.java
+++ b/java/core/src/main/java/org/apache/arrow/adbc/core/TypedKey.java
@@ -19,6 +19,8 @@ package org.apache.arrow.adbc.core;
import java.util.Map;
import java.util.Objects;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
/**
* A typesafe option key.
@@ -45,8 +47,8 @@ public final class TypedKey<T> {
*
* @throws ClassCastException if the value is of the wrong type.
*/
- public T get(Map<String, Object> options) {
- Object value = options.get(key);
+ public @Nullable T get(Map<String, Object> options) {
+ @Nullable Object value = options.get(key);
if (value == null) {
return null;
}
@@ -59,12 +61,12 @@ public final class TypedKey<T> {
* @param options The options.
* @param value The option value.
*/
- public void set(Map<String, Object> options, T value) {
+ public void set(Map<String, Object> options, @NonNull T value) {
options.put(key, value);
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
diff --git a/java/driver-manager/pom.xml b/java/driver-manager/pom.xml
index 8a083a58..589bfe40 100644
--- a/java/driver-manager/pom.xml
+++ b/java/driver-manager/pom.xml
@@ -28,6 +28,12 @@
<artifactId>adbc-core</artifactId>
</dependency>
+ <!-- Static analysis and linting -->
+ <dependency>
+ <groupId>org.checkerframework</groupId>
+ <artifactId>checker-qual</artifactId>
+ </dependency>
+
<!-- Testing -->
<dependency>
<groupId>org.assertj</groupId>
diff --git
a/java/driver-manager/src/main/java/org/apache/arrow/adbc/drivermanager/AdbcDriverManager.java
b/java/driver-manager/src/main/java/org/apache/arrow/adbc/drivermanager/AdbcDriverManager.java
index c2e20efd..5068fb9a 100644
---
a/java/driver-manager/src/main/java/org/apache/arrow/adbc/drivermanager/AdbcDriverManager.java
+++
b/java/driver-manager/src/main/java/org/apache/arrow/adbc/drivermanager/AdbcDriverManager.java
@@ -27,6 +27,7 @@ import org.apache.arrow.adbc.core.AdbcDriver;
import org.apache.arrow.adbc.core.AdbcException;
import org.apache.arrow.adbc.core.AdbcStatusCode;
import org.apache.arrow.memory.BufferAllocator;
+import org.checkerframework.checker.nullness.qual.Nullable;
/** Instantiate connections to ABDC databases generically based on driver
name. */
public final class AdbcDriverManager {
@@ -38,10 +39,13 @@ public final class AdbcDriverManager {
driverFactoryFunctions = new ConcurrentHashMap<>();
final ServiceLoader<AdbcDriverFactory> serviceLoader =
ServiceLoader.load(AdbcDriverFactory.class);
- serviceLoader.forEach(
- driverFactory ->
- driverFactoryFunctions.putIfAbsent(
- driverFactory.getClass().getCanonicalName(),
driverFactory::getDriver));
+ for (AdbcDriverFactory driverFactory : serviceLoader) {
+ final @Nullable String className =
driverFactory.getClass().getCanonicalName();
+ if (className == null) {
+ throw new RuntimeException("Class has no canonical name");
+ }
+ driverFactoryFunctions.putIfAbsent(className, driverFactory::getDriver);
+ }
}
/**
@@ -77,7 +81,7 @@ public final class AdbcDriverManager {
* fully-qualified class name of an AdbcDriverFactory class.
* @return A function to construct an AdbcDriver from a BufferAllocator, or
null if not found.
*/
- Function<BufferAllocator, AdbcDriver> lookupDriver(String driverFactoryName)
{
+ @Nullable Function<BufferAllocator, AdbcDriver> lookupDriver(String
driverFactoryName) {
return driverFactoryFunctions.get(driverFactoryName);
}
diff --git
a/java/driver/flight-sql-validation/src/test/java/org/apache/arrow/adbc/driver/flightsql/FlightSqlQuirks.java
b/java/driver/flight-sql-validation/src/test/java/org/apache/arrow/adbc/driver/flightsql/FlightSqlQuirks.java
index d3f79889..fe01a18c 100644
---
a/java/driver/flight-sql-validation/src/test/java/org/apache/arrow/adbc/driver/flightsql/FlightSqlQuirks.java
+++
b/java/driver/flight-sql-validation/src/test/java/org/apache/arrow/adbc/driver/flightsql/FlightSqlQuirks.java
@@ -36,8 +36,8 @@ public class FlightSqlQuirks extends SqlValidationQuirks {
static String getFlightLocation() {
final String location = System.getenv(FLIGHT_SQL_LOCATION_ENV_VAR);
- Assumptions.assumeFalse(
- location == null || location.isEmpty(),
+ Assumptions.assumeTrue(
+ location != null && !location.isEmpty(),
"Flight SQL server not found, set " + FLIGHT_SQL_LOCATION_ENV_VAR);
return location;
}
diff --git a/java/driver/flight-sql/pom.xml b/java/driver/flight-sql/pom.xml
index ceceaa82..e410c2a1 100644
--- a/java/driver/flight-sql/pom.xml
+++ b/java/driver/flight-sql/pom.xml
@@ -67,6 +67,12 @@
<artifactId>adbc-sql</artifactId>
</dependency>
+ <!-- Static analysis and linting -->
+ <dependency>
+ <groupId>org.checkerframework</groupId>
+ <artifactId>checker-qual</artifactId>
+ </dependency>
+
<!-- Testing -->
<dependency>
<groupId>org.assertj</groupId>
diff --git
a/java/driver/flight-sql/src/main/java/org/apache/arrow/adbc/driver/flightsql/FlightInfoReader.java
b/java/driver/flight-sql/src/main/java/org/apache/arrow/adbc/driver/flightsql/FlightInfoReader.java
index 6850be06..1de006f2 100644
---
a/java/driver/flight-sql/src/main/java/org/apache/arrow/adbc/driver/flightsql/FlightInfoReader.java
+++
b/java/driver/flight-sql/src/main/java/org/apache/arrow/adbc/driver/flightsql/FlightInfoReader.java
@@ -34,6 +34,7 @@ import org.apache.arrow.vector.VectorUnloader;
import org.apache.arrow.vector.ipc.ArrowReader;
import org.apache.arrow.vector.ipc.message.ArrowRecordBatch;
import org.apache.arrow.vector.types.pojo.Schema;
+import org.checkerframework.checker.nullness.qual.Nullable;
/** An ArrowReader that wraps a FlightInfo. */
public class FlightInfoReader extends ArrowReader {
@@ -45,6 +46,8 @@ public class FlightInfoReader extends ArrowReader {
private FlightStream currentStream;
private long bytesRead;
+ @SuppressWarnings(
+ "method.invocation") // Checker Framework does not like the
ensureInitialized call
FlightInfoReader(
BufferAllocator allocator,
FlightSqlClientWithCallOptions client,
@@ -118,8 +121,12 @@ public class FlightInfoReader extends ArrowReader {
Collections.shuffle(locations);
IOException failure = null;
for (final Location location : locations) {
+ final @Nullable FlightSqlClientWithCallOptions client =
clientCache.get(location);
+ if (client == null) {
+ throw new IllegalStateException("Could not connect to " + location);
+ }
try {
- return
Objects.requireNonNull(clientCache.get(location)).getStream(endpoint.getTicket());
+ return client.getStream(endpoint.getTicket());
} catch (RuntimeException e) {
// Also handles CompletionException (from clientCache#get),
FlightRuntimeException
if (failure == null) {
@@ -131,6 +138,9 @@ public class FlightInfoReader extends ArrowReader {
}
}
}
+ if (failure == null) {
+ throw new IllegalStateException("FlightEndpoint had no locations");
+ }
throw Objects.requireNonNull(failure);
}
}
diff --git
a/java/driver/flight-sql/src/main/java/org/apache/arrow/adbc/driver/flightsql/FlightSqlConnection.java
b/java/driver/flight-sql/src/main/java/org/apache/arrow/adbc/driver/flightsql/FlightSqlConnection.java
index c079060e..586d31e0 100644
---
a/java/driver/flight-sql/src/main/java/org/apache/arrow/adbc/driver/flightsql/FlightSqlConnection.java
+++
b/java/driver/flight-sql/src/main/java/org/apache/arrow/adbc/driver/flightsql/FlightSqlConnection.java
@@ -55,24 +55,26 @@ import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.util.AutoCloseables;
import org.apache.arrow.vector.VectorSchemaRoot;
import org.apache.arrow.vector.ipc.ArrowReader;
+import org.checkerframework.checker.initialization.qual.UnknownInitialization;
+import org.checkerframework.checker.nullness.qual.Nullable;
public class FlightSqlConnection implements AdbcConnection {
private final BufferAllocator allocator;
- private final AtomicInteger counter;
+ private final AtomicInteger counter = new AtomicInteger(0);
private final FlightSqlClientWithCallOptions client;
private final SqlQuirks quirks;
private final Map<String, Object> parameters;
private final LoadingCache<Location, FlightSqlClientWithCallOptions>
clientCache;
// Cached data to use across additional connections.
- private ClientCookieMiddleware.Factory cookieMiddlewareFactory;
+ private ClientCookieMiddleware.@Nullable Factory cookieMiddlewareFactory;
private CallOption[] callOptions;
// Used to cache the InputStream content as a byte array since
// subsequent connections may need to use it but it is supplied as a stream.
- private byte[] mtlsCertChainBytes;
- private byte[] mtlsPrivateKeyBytes;
- private byte[] tlsRootCertsBytes;
+ private byte @Nullable [] mtlsCertChainBytes;
+ private byte @Nullable [] mtlsPrivateKeyBytes;
+ private byte @Nullable [] tlsRootCertsBytes;
FlightSqlConnection(
BufferAllocator allocator,
@@ -81,16 +83,18 @@ public class FlightSqlConnection implements AdbcConnection {
Map<String, Object> parameters)
throws AdbcException {
this.allocator = allocator;
- this.counter = new AtomicInteger(0);
this.quirks = quirks;
this.parameters = parameters;
+ this.callOptions = new CallOption[0];
FlightSqlClient flightSqlClient = new
FlightSqlClient(createInitialConnection(location));
this.client = new FlightSqlClientWithCallOptions(flightSqlClient,
callOptions);
this.clientCache =
Caffeine.newBuilder()
.expireAfterAccess(5, TimeUnit.MINUTES)
.removalListener(
- (Location key, FlightSqlClientWithCallOptions value,
RemovalCause cause) -> {
+ (@Nullable Location key,
+ @Nullable FlightSqlClientWithCallOptions value,
+ RemovalCause cause) -> {
if (value == null) return;
try {
value.close();
@@ -153,7 +157,7 @@ public class FlightSqlConnection implements AdbcConnection {
}
@Override
- public ArrowReader getInfo(int[] infoCodes) throws AdbcException {
+ public ArrowReader getInfo(int @Nullable [] infoCodes) throws AdbcException {
try (InfoMetadataBuilder builder = new InfoMetadataBuilder(allocator,
client, infoCodes)) {
try (final VectorSchemaRoot root = builder.build()) {
return RootArrowReader.fromRoot(allocator, root);
@@ -195,24 +199,28 @@ public class FlightSqlConnection implements
AdbcConnection {
* Initialize cached data to share between connections and create, test, and
authenticate the
* first connection.
*/
- private FlightClient createInitialConnection(Location location) throws
AdbcException {
+ private FlightClient createInitialConnection(
+ @UnknownInitialization FlightSqlConnection this, Location location)
throws AdbcException {
// Setup cached pre-connection properties.
try {
- final InputStream mtlsCertChain =
- FlightSqlConnectionProperties.MTLS_CERT_CHAIN.get(parameters);
- if (mtlsCertChain != null) {
- this.mtlsCertChainBytes = inputStreamToBytes(mtlsCertChain);
- }
+ if (parameters != null) {
+ final InputStream mtlsCertChain =
+ FlightSqlConnectionProperties.MTLS_CERT_CHAIN.get(parameters);
+ if (mtlsCertChain != null) {
+ this.mtlsCertChainBytes = inputStreamToBytes(mtlsCertChain);
+ }
- final InputStream mtlsPrivateKey =
- FlightSqlConnectionProperties.MTLS_PRIVATE_KEY.get(parameters);
- if (mtlsPrivateKey != null) {
- this.mtlsPrivateKeyBytes = inputStreamToBytes(mtlsPrivateKey);
- }
+ final InputStream mtlsPrivateKey =
+ FlightSqlConnectionProperties.MTLS_PRIVATE_KEY.get(parameters);
+ if (mtlsPrivateKey != null) {
+ this.mtlsPrivateKeyBytes = inputStreamToBytes(mtlsPrivateKey);
+ }
- final InputStream tlsRootCerts =
FlightSqlConnectionProperties.TLS_ROOT_CERTS.get(parameters);
- if (tlsRootCerts != null) {
- this.tlsRootCertsBytes = inputStreamToBytes(tlsRootCerts);
+ final InputStream tlsRootCerts =
+ FlightSqlConnectionProperties.TLS_ROOT_CERTS.get(parameters);
+ if (tlsRootCerts != null) {
+ this.tlsRootCertsBytes = inputStreamToBytes(tlsRootCerts);
+ }
}
} catch (IOException ex) {
throw new AdbcException(
@@ -227,10 +235,12 @@ public class FlightSqlConnection implements
AdbcConnection {
0);
}
- final boolean useCookieMiddleware =
-
Boolean.TRUE.equals(FlightSqlConnectionProperties.WITH_COOKIE_MIDDLEWARE.get(parameters));
- if (useCookieMiddleware) {
- this.cookieMiddlewareFactory = new ClientCookieMiddleware.Factory();
+ if (parameters != null) {
+ final boolean useCookieMiddleware =
+
Boolean.TRUE.equals(FlightSqlConnectionProperties.WITH_COOKIE_MIDDLEWARE.get(parameters));
+ if (useCookieMiddleware) {
+ this.cookieMiddlewareFactory = new ClientCookieMiddleware.Factory();
+ }
}
// Build the client using the above properties.
@@ -284,7 +294,11 @@ public class FlightSqlConnection implements AdbcConnection
{
}
/** Returns a yet-to-be authenticated FlightClient */
- private FlightClient buildClient(Location location) throws AdbcException {
+ private FlightClient buildClient(
+ @UnknownInitialization FlightSqlConnection this, Location location)
throws AdbcException {
+ if (allocator == null) {
+ throw new IllegalStateException("Internal error: allocator was not
initialized");
+ }
final FlightClient.Builder builder =
FlightClient.builder()
.allocator(
@@ -327,13 +341,15 @@ public class FlightSqlConnection implements
AdbcConnection {
builder.trustedCertificates(new ByteArrayInputStream(tlsRootCertsBytes));
}
- if
(Boolean.TRUE.equals(FlightSqlConnectionProperties.TLS_SKIP_VERIFY.get(parameters)))
{
- builder.verifyServer(false);
- }
+ if (parameters != null) {
+ if
(Boolean.TRUE.equals(FlightSqlConnectionProperties.TLS_SKIP_VERIFY.get(parameters)))
{
+ builder.verifyServer(false);
+ }
- String hostnameOverride =
FlightSqlConnectionProperties.TLS_OVERRIDE_HOSTNAME.get(parameters);
- if (hostnameOverride != null) {
- builder.overrideHostname(hostnameOverride);
+ String hostnameOverride =
FlightSqlConnectionProperties.TLS_OVERRIDE_HOSTNAME.get(parameters);
+ if (hostnameOverride != null) {
+ builder.overrideHostname(hostnameOverride);
+ }
}
// Setup cookies if needed.
diff --git
a/java/driver/flight-sql/src/main/java/org/apache/arrow/adbc/driver/flightsql/FlightSqlDriverUtil.java
b/java/driver/flight-sql/src/main/java/org/apache/arrow/adbc/driver/flightsql/FlightSqlDriverUtil.java
index a4ea23dd..ae776d9e 100644
---
a/java/driver/flight-sql/src/main/java/org/apache/arrow/adbc/driver/flightsql/FlightSqlDriverUtil.java
+++
b/java/driver/flight-sql/src/main/java/org/apache/arrow/adbc/driver/flightsql/FlightSqlDriverUtil.java
@@ -24,13 +24,18 @@ import org.apache.arrow.adbc.core.AdbcStatusCode;
import org.apache.arrow.adbc.core.ErrorDetail;
import org.apache.arrow.flight.FlightRuntimeException;
import org.apache.arrow.flight.FlightStatusCode;
+import org.checkerframework.checker.nullness.qual.Nullable;
final class FlightSqlDriverUtil {
private FlightSqlDriverUtil() {
throw new AssertionError("Do not instantiate this class");
}
- static String prefixExceptionMessage(final String s) {
+ static String prefixExceptionMessage(final @Nullable String s) {
+ // Allow null since Throwable#getMessage may be null
+ if (s == null) {
+ return "[Flight SQL] (No or unknown error)";
+ }
return "[Flight SQL] " + s;
}
diff --git
a/java/driver/flight-sql/src/main/java/org/apache/arrow/adbc/driver/flightsql/FlightSqlStatement.java
b/java/driver/flight-sql/src/main/java/org/apache/arrow/adbc/driver/flightsql/FlightSqlStatement.java
index 77cb2622..6a00ca23 100644
---
a/java/driver/flight-sql/src/main/java/org/apache/arrow/adbc/driver/flightsql/FlightSqlStatement.java
+++
b/java/driver/flight-sql/src/main/java/org/apache/arrow/adbc/driver/flightsql/FlightSqlStatement.java
@@ -40,6 +40,7 @@ import org.apache.arrow.util.AutoCloseables;
import org.apache.arrow.vector.VectorSchemaRoot;
import org.apache.arrow.vector.types.pojo.Field;
import org.apache.arrow.vector.types.pojo.Schema;
+import org.checkerframework.checker.nullness.qual.Nullable;
public class FlightSqlStatement implements AdbcStatement {
private final BufferAllocator allocator;
@@ -48,11 +49,11 @@ public class FlightSqlStatement implements AdbcStatement {
private final SqlQuirks quirks;
// State for SQL queries
- private String sqlQuery;
- private FlightSqlClient.PreparedStatement preparedStatement;
+ private @Nullable String sqlQuery;
+ private FlightSqlClient.@Nullable PreparedStatement preparedStatement;
// State for bulk ingest
- private BulkState bulkOperation;
- private VectorSchemaRoot bindRoot;
+ private @Nullable BulkState bulkOperation;
+ private @Nullable VectorSchemaRoot bindRoot;
FlightSqlStatement(
BufferAllocator allocator,
@@ -64,6 +65,9 @@ public class FlightSqlStatement implements AdbcStatement {
this.clientCache = clientCache;
this.quirks = quirks;
this.sqlQuery = null;
+ this.preparedStatement = null;
+ this.bulkOperation = null;
+ this.bindRoot = null;
}
static FlightSqlStatement ingestRoot(
@@ -76,9 +80,7 @@ public class FlightSqlStatement implements AdbcStatement {
Objects.requireNonNull(targetTableName);
final FlightSqlStatement statement =
new FlightSqlStatement(allocator, client, clientCache, quirks);
- statement.bulkOperation = new BulkState();
- statement.bulkOperation.mode = mode;
- statement.bulkOperation.targetTable = targetTableName;
+ statement.bulkOperation = new BulkState(mode, targetTableName);
return statement;
}
@@ -96,7 +98,8 @@ public class FlightSqlStatement implements AdbcStatement {
bindRoot = root;
}
- private void createBulkTable() throws AdbcException {
+ private void createBulkTable(BulkState bulkOperation, VectorSchemaRoot
bindRoot)
+ throws AdbcException {
final StringBuilder create = new StringBuilder("CREATE TABLE ");
create.append(bulkOperation.targetTable);
create.append(" (");
@@ -128,20 +131,21 @@ public class FlightSqlStatement implements AdbcStatement {
}
}
- private UpdateResult executeBulk() throws AdbcException {
+ private UpdateResult executeBulk(BulkState bulkOperation) throws
AdbcException {
if (bindRoot == null) {
throw AdbcException.invalidState("[Flight SQL] Must call bind() before
bulk insert");
}
+ final VectorSchemaRoot bindParams = bindRoot;
if (bulkOperation.mode == BulkIngestMode.CREATE) {
- createBulkTable();
+ createBulkTable(bulkOperation, bindParams);
}
// XXX: potential injection
final StringBuilder insert = new StringBuilder("INSERT INTO ");
insert.append(bulkOperation.targetTable);
insert.append(" VALUES (");
- for (int col = 0; col < bindRoot.getFieldVectors().size(); col++) {
+ for (int col = 0; col < bindParams.getFieldVectors().size(); col++) {
if (col > 0) {
insert.append(", ");
}
@@ -163,7 +167,7 @@ public class FlightSqlStatement implements AdbcStatement {
}
try {
try {
- statement.setParameters(new NonOwningRoot(bindRoot));
+ statement.setParameters(new NonOwningRoot(bindParams));
statement.executeUpdate();
} finally {
statement.close();
@@ -177,7 +181,7 @@ public class FlightSqlStatement implements AdbcStatement {
}
throw FlightSqlDriverUtil.fromFlightException(e);
}
- return new UpdateResult(bindRoot.getRowCount());
+ return new UpdateResult(bindParams.getRowCount());
}
@FunctionalInterface
@@ -191,13 +195,14 @@ public class FlightSqlStatement implements AdbcStatement {
throws AdbcException {
try {
if (preparedStatement != null) {
+ FlightSqlClient.PreparedStatement prepared = preparedStatement;
// TODO: This binds only the LAST row
// See https://lists.apache.org/thread/47zfk3xooojckvfjq2h6ldlqkjrqnsjt
// "[DISC] Flight SQL: clarifying prepared statements with parameters
and result sets"
if (bindRoot != null) {
- preparedStatement.setParameters(new NonOwningRoot(bindRoot));
+ prepared.setParameters(new NonOwningRoot(bindRoot));
}
- return doPrepared.execute(preparedStatement);
+ return doPrepared.execute(prepared);
} else {
return doRegular.execute(client);
}
@@ -212,8 +217,8 @@ public class FlightSqlStatement implements AdbcStatement {
} else if (sqlQuery == null) {
throw AdbcException.invalidState("[Flight SQL] Must setSqlQuery() before
execute");
}
- return execute(
- FlightSqlClient.PreparedStatement::execute, (client) ->
client.execute(sqlQuery));
+ final String query = sqlQuery;
+ return execute(FlightSqlClient.PreparedStatement::execute, (client) ->
client.execute(query));
}
@Override
@@ -253,18 +258,20 @@ public class FlightSqlStatement implements AdbcStatement {
} else if (sqlQuery == null) {
throw AdbcException.invalidState("[Flight SQL] Must setSqlQuery() before
execute");
}
+ final String query = sqlQuery;
return execute(
FlightSqlClient.PreparedStatement::getResultSetSchema,
- (client) -> client.getExecuteSchema(sqlQuery).getSchema());
+ (client) -> client.getExecuteSchema(query).getSchema());
}
@Override
public UpdateResult executeUpdate() throws AdbcException {
if (bulkOperation != null) {
- return executeBulk();
+ return executeBulk(bulkOperation);
} else if (sqlQuery == null) {
throw AdbcException.invalidState("[Flight SQL] Must setSqlQuery() before
executeUpdate");
}
+ final String query = sqlQuery;
long updatedRows =
execute(
(preparedStatement) -> {
@@ -275,7 +282,7 @@ public class FlightSqlStatement implements AdbcStatement {
throw FlightSqlDriverUtil.fromFlightException(e);
}
},
- (client) -> client.executeUpdate(sqlQuery));
+ (client) -> client.executeUpdate(query));
return new UpdateResult(updatedRows);
}
@@ -303,12 +310,20 @@ public class FlightSqlStatement implements AdbcStatement {
@Override
public void close() throws Exception {
- AutoCloseables.close(preparedStatement);
+ // TODO(https://github.com/apache/arrow/issues/39814): this is annotated
wrongly upstream
+ if (preparedStatement != null) {
+ AutoCloseables.close(preparedStatement);
+ }
}
private static final class BulkState {
- public BulkIngestMode mode;
+ BulkIngestMode mode;
String targetTable;
+
+ public BulkState(BulkIngestMode mode, String targetTableName) {
+ this.mode = mode;
+ this.targetTable = targetTableName;
+ }
}
/** A VectorSchemaRoot which does not own its data. */
diff --git
a/java/driver/flight-sql/src/main/java/org/apache/arrow/adbc/driver/flightsql/InfoMetadataBuilder.java
b/java/driver/flight-sql/src/main/java/org/apache/arrow/adbc/driver/flightsql/InfoMetadataBuilder.java
index aef679f6..063b7066 100644
---
a/java/driver/flight-sql/src/main/java/org/apache/arrow/adbc/driver/flightsql/InfoMetadataBuilder.java
+++
b/java/driver/flight-sql/src/main/java/org/apache/arrow/adbc/driver/flightsql/InfoMetadataBuilder.java
@@ -38,6 +38,7 @@ import org.apache.arrow.vector.UInt4Vector;
import org.apache.arrow.vector.VarCharVector;
import org.apache.arrow.vector.VectorSchemaRoot;
import org.apache.arrow.vector.complex.DenseUnionVector;
+import org.checkerframework.checker.nullness.qual.Nullable;
/** Helper class to track state needed to build up the info structure. */
final class InfoMetadataBuilder implements AutoCloseable {
@@ -45,6 +46,7 @@ final class InfoMetadataBuilder implements AutoCloseable {
private static final Map<Integer, Integer> ADBC_TO_FLIGHT_SQL_CODES = new
HashMap<>();
private static final Map<Integer, AddInfo> SUPPORTED_CODES = new HashMap<>();
+ private final BufferAllocator allocator;
private final Collection<Integer> requestedCodes;
private final FlightSqlClientWithCallOptions client;
private VectorSchemaRoot root;
@@ -80,7 +82,10 @@ final class InfoMetadataBuilder implements AutoCloseable {
}
InfoMetadataBuilder(
- BufferAllocator allocator, FlightSqlClientWithCallOptions client, int[]
infoCodes) {
+ BufferAllocator allocator,
+ FlightSqlClientWithCallOptions client,
+ int @Nullable [] infoCodes) {
+ this.allocator = allocator;
if (infoCodes == null) {
this.requestedCodes = new ArrayList<>(SUPPORTED_CODES.keySet());
this.requestedCodes.add(AdbcInfoCode.DRIVER_NAME.getValue());
@@ -145,7 +150,12 @@ final class InfoMetadataBuilder implements AutoCloseable {
root.setRowCount(dstIndex);
VectorSchemaRoot result = root;
- root = null;
+ try {
+ root = VectorSchemaRoot.create(StandardSchemas.GET_INFO_SCHEMA,
allocator);
+ } catch (RuntimeException e) {
+ result.close();
+ throw e;
+ }
return result;
}
diff --git
a/java/driver/jdbc-validation-postgresql/src/test/java/org/apache/arrow/adbc/driver/jdbc/postgresql/PostgreSqlTypeTest.java
b/java/driver/jdbc-validation-postgresql/src/test/java/org/apache/arrow/adbc/driver/jdbc/postgresql/PostgreSqlTypeTest.java
index 5b8357d7..5fd8f17c 100644
---
a/java/driver/jdbc-validation-postgresql/src/test/java/org/apache/arrow/adbc/driver/jdbc/postgresql/PostgreSqlTypeTest.java
+++
b/java/driver/jdbc-validation-postgresql/src/test/java/org/apache/arrow/adbc/driver/jdbc/postgresql/PostgreSqlTypeTest.java
@@ -85,27 +85,27 @@ class PostgreSqlTypeTest extends AbstractSqlTypeTest {
protected void timestamp3WithoutTimeZoneType() throws Exception {
final Schema schema = connection.getTableSchema(null, null,
"adbc_alltypes");
assertThat(schema.findField("timestamp_without_time_zone_p3_t").getType())
- .isEqualTo(new ArrowType.Timestamp(TimeUnit.MICROSECOND, null));
+ .isEqualTo(new ArrowType.Timestamp(TimeUnit.MILLISECOND, null));
}
@Test
protected void timestamp2WithoutTimeZoneType() throws Exception {
final Schema schema = connection.getTableSchema(null, null,
"adbc_alltypes");
assertThat(schema.findField("timestamp_without_time_zone_p2_t").getType())
- .isEqualTo(new ArrowType.Timestamp(TimeUnit.MICROSECOND, null));
+ .isEqualTo(new ArrowType.Timestamp(TimeUnit.MILLISECOND, null));
}
@Test
protected void timestamp1WithoutTimeZoneType() throws Exception {
final Schema schema = connection.getTableSchema(null, null,
"adbc_alltypes");
assertThat(schema.findField("timestamp_without_time_zone_p1_t").getType())
- .isEqualTo(new ArrowType.Timestamp(TimeUnit.MICROSECOND, null));
+ .isEqualTo(new ArrowType.Timestamp(TimeUnit.MILLISECOND, null));
}
@Test
protected void timestamp0WithoutTimeZoneType() throws Exception {
final Schema schema = connection.getTableSchema(null, null,
"adbc_alltypes");
assertThat(schema.findField("timestamp_without_time_zone_p0_t").getType())
- .isEqualTo(new ArrowType.Timestamp(TimeUnit.MICROSECOND, null));
+ .isEqualTo(new ArrowType.Timestamp(TimeUnit.SECOND, null));
}
}
diff --git a/java/driver/jdbc/pom.xml b/java/driver/jdbc/pom.xml
index 6b9eda4a..5a415c38 100644
--- a/java/driver/jdbc/pom.xml
+++ b/java/driver/jdbc/pom.xml
@@ -51,6 +51,12 @@
<artifactId>adbc-sql</artifactId>
</dependency>
+ <!-- Static analysis and linting -->
+ <dependency>
+ <groupId>org.checkerframework</groupId>
+ <artifactId>checker-qual</artifactId>
+ </dependency>
+
<!-- Testing -->
<dependency>
<groupId>org.assertj</groupId>
diff --git
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/InfoMetadataBuilder.java
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/InfoMetadataBuilder.java
index ae5ec226..3fcab19c 100644
---
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/InfoMetadataBuilder.java
+++
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/InfoMetadataBuilder.java
@@ -20,6 +20,7 @@ import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@@ -35,12 +36,14 @@ import org.apache.arrow.vector.UInt4Vector;
import org.apache.arrow.vector.VarCharVector;
import org.apache.arrow.vector.VectorSchemaRoot;
import org.apache.arrow.vector.complex.DenseUnionVector;
+import org.checkerframework.checker.nullness.qual.Nullable;
/** Helper class to track state needed to build up the info structure. */
final class InfoMetadataBuilder implements AutoCloseable {
private static final byte STRING_VALUE_TYPE_ID = (byte) 0;
private static final byte BIGINT_VALUE_TYPE_ID = (byte) 2;
private static final Map<Integer, AddInfo> SUPPORTED_CODES = new HashMap<>();
+ private final BufferAllocator allocator;
private final Collection<Integer> requestedCodes;
private final DatabaseMetaData dbmd;
private VectorSchemaRoot root;
@@ -85,12 +88,14 @@ final class InfoMetadataBuilder implements AutoCloseable {
});
}
- InfoMetadataBuilder(BufferAllocator allocator, Connection connection, int[]
infoCodes)
+ InfoMetadataBuilder(BufferAllocator allocator, Connection connection, int
@Nullable [] infoCodes)
throws SQLException {
- this.requestedCodes =
- infoCodes == null
- ? SUPPORTED_CODES.keySet()
- : IntStream.of(infoCodes).boxed().collect(Collectors.toList());
+ this.allocator = allocator;
+ if (infoCodes == null) {
+ this.requestedCodes = new ArrayList<>(SUPPORTED_CODES.keySet());
+ } else {
+ this.requestedCodes =
IntStream.of(infoCodes).boxed().collect(Collectors.toList());
+ }
this.root = VectorSchemaRoot.create(StandardSchemas.GET_INFO_SCHEMA,
allocator);
this.dbmd = connection.getMetaData();
this.infoCodes = (UInt4Vector) root.getVector(0);
@@ -130,7 +135,12 @@ final class InfoMetadataBuilder implements AutoCloseable {
}
root.setRowCount(rowIndex);
VectorSchemaRoot result = root;
- root = null;
+ try {
+ root = VectorSchemaRoot.create(StandardSchemas.GET_INFO_SCHEMA,
allocator);
+ } catch (RuntimeException e) {
+ result.close();
+ throw e;
+ }
return result;
}
diff --git
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcArrowReader.java
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcArrowReader.java
index aba972a9..5d53e461 100644
---
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcArrowReader.java
+++
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcArrowReader.java
@@ -32,6 +32,7 @@ import org.apache.arrow.vector.VectorUnloader;
import org.apache.arrow.vector.ipc.ArrowReader;
import org.apache.arrow.vector.ipc.message.ArrowRecordBatch;
import org.apache.arrow.vector.types.pojo.Schema;
+import org.checkerframework.checker.nullness.qual.Nullable;
/** An ArrowReader that wraps a JDBC ResultSet. */
public class JdbcArrowReader extends ArrowReader {
@@ -39,7 +40,9 @@ public class JdbcArrowReader extends ArrowReader {
private final Schema schema;
private long bytesRead;
- JdbcArrowReader(BufferAllocator allocator, ResultSet resultSet, Schema
overrideSchema)
+ // ensureInitialized() call isn't annotated right
+ @SuppressWarnings({"under.initialization", "method.invocation"})
+ JdbcArrowReader(BufferAllocator allocator, ResultSet resultSet, @Nullable
Schema overrideSchema)
throws AdbcException {
super(allocator);
final JdbcToArrowConfig config = makeJdbcConfig(allocator);
diff --git
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcBindReader.java
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcBindReader.java
index 167d3f2d..0875b610 100644
---
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcBindReader.java
+++
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcBindReader.java
@@ -31,13 +31,14 @@ import org.apache.arrow.vector.VectorUnloader;
import org.apache.arrow.vector.ipc.ArrowReader;
import org.apache.arrow.vector.ipc.message.ArrowRecordBatch;
import org.apache.arrow.vector.types.pojo.Schema;
+import org.checkerframework.checker.nullness.qual.Nullable;
/** An Arrow reader that binds parameters. */
final class JdbcBindReader extends ArrowReader {
private final PreparedStatement statement;
private final JdbcParameterBinder binder;
- private ResultSet currentResultSet;
- private ArrowVectorIterator currentSource;
+ private @Nullable ResultSet currentResultSet;
+ private @Nullable ArrowVectorIterator currentSource;
JdbcBindReader(
BufferAllocator allocator, PreparedStatement statement, VectorSchemaRoot
bindParameters) {
@@ -53,6 +54,9 @@ final class JdbcBindReader extends ArrowReader {
return false;
}
}
+ if (currentSource == null) {
+ throw new IllegalStateException("Source was null after advancing
reader");
+ }
try (final VectorSchemaRoot root = currentSource.next()) {
try (final ArrowRecordBatch batch = new
VectorUnloader(root).getRecordBatch()) {
@@ -85,6 +89,9 @@ final class JdbcBindReader extends ArrowReader {
if (!advance()) {
throw new IOException("Parameter set is empty!");
}
+ if (currentResultSet == null) {
+ throw new IllegalStateException("Driver returned null result set");
+ }
return JdbcToArrowUtils.jdbcToArrowSchema(
currentResultSet.getMetaData(), JdbcToArrowUtils.getUtcCalendar());
} catch (SQLException e) {
@@ -95,8 +102,10 @@ final class JdbcBindReader extends ArrowReader {
private boolean advance() throws IOException {
try {
if (binder.next()) {
- if (currentResultSet != null) {
+ if (currentSource != null) {
currentSource.close();
+ }
+ if (currentResultSet != null) {
currentResultSet.close();
}
currentResultSet = statement.executeQuery();
diff --git
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcConnection.java
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcConnection.java
index 8f66c154..2987815d 100644
---
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcConnection.java
+++
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcConnection.java
@@ -21,6 +21,7 @@ import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -49,6 +50,7 @@ import org.apache.arrow.vector.types.pojo.Field;
import org.apache.arrow.vector.types.pojo.FieldType;
import org.apache.arrow.vector.types.pojo.Schema;
import org.apache.arrow.vector.util.Text;
+import org.checkerframework.checker.nullness.qual.Nullable;
public class JdbcConnection implements AdbcConnection {
private final BufferAllocator allocator;
@@ -90,7 +92,7 @@ public class JdbcConnection implements AdbcConnection {
}
@Override
- public ArrowReader getInfo(int[] infoCodes) throws AdbcException {
+ public ArrowReader getInfo(int @Nullable [] infoCodes) throws AdbcException {
try {
try (final VectorSchemaRoot root =
new InfoMetadataBuilder(allocator, connection, infoCodes).build()) {
@@ -134,8 +136,15 @@ public class JdbcConnection implements AdbcConnection {
short key;
long value;
boolean multiColumn = false;
+
+ public Statistic(String table, String column) {
+ this.table = table;
+ this.column = column;
+ }
}
+ // We use null keys intentionally with HashMap, but the annotations don't
technically allow this
+ @SuppressWarnings("argument")
@Override
public ArrowReader getStatistics(
String catalogPattern, String dbSchemaPattern, String tableNamePattern,
boolean approximate)
@@ -163,7 +172,7 @@ public class JdbcConnection implements AdbcConnection {
Map<String, Map<String, Map<String, Statistic>>> allStatistics = new
HashMap<>();
while (rs.next()) {
- String catalog = rs.getString(1);
+ @Nullable String catalog = rs.getString(1);
String schema = rs.getString(2);
String table = rs.getString(3);
String index = rs.getString(6);
@@ -171,6 +180,15 @@ public class JdbcConnection implements AdbcConnection {
String column = rs.getString(9);
long cardinality = rs.getLong(11);
+ if (table == null || column == null) {
+ throw new AdbcException(
+ JdbcDriverUtil.prefixExceptionMessage("JDBC driver returned null
table/column name"),
+ null,
+ AdbcStatusCode.INTERNAL,
+ null,
+ 0);
+ }
+
if (!allStatistics.containsKey(catalog)) {
allStatistics.put(catalog, new HashMap<>());
}
@@ -181,7 +199,8 @@ public class JdbcConnection implements AdbcConnection {
}
Map<String, Statistic> schemaStats = catalogStats.get(schema);
- Statistic statistic = schemaStats.getOrDefault(index, new Statistic());
+ Statistic statistic = schemaStats.getOrDefault(index, new
Statistic(table, column));
+ assert statistic != null; // for checker-framework
if (schemaStats.containsKey(index)) {
// Multi-column index, ignore it
statistic.multiColumn = true;
@@ -315,7 +334,10 @@ public class JdbcConnection implements AdbcConnection {
.getMetaData()
.getColumns(catalog, dbSchema, tableName, /*columnNamePattern*/
null)) {
while (rs.next()) {
- final String fieldName = rs.getString("COLUMN_NAME");
+ @Nullable String fieldName = rs.getString("COLUMN_NAME");
+ if (fieldName == null) {
+ fieldName = "";
+ }
final JdbcFieldInfoExtra fieldInfoExtra = new JdbcFieldInfoExtra(rs);
final ArrowType arrowType =
quirks.getTypeConverter().apply(fieldInfoExtra);
@@ -329,12 +351,10 @@ public class JdbcConnection implements AdbcConnection {
final Field field =
new Field(
fieldName,
- new FieldType(
- fieldInfoExtra.isNullable() !=
DatabaseMetaData.columnNoNulls,
- arrowType, /*dictionary*/
- null, /*metadata*/
- null),
- /*children*/ null);
+ fieldInfoExtra.isNullable() == DatabaseMetaData.columnNoNulls
+ ? FieldType.notNullable(arrowType)
+ : FieldType.nullable(arrowType),
+ Collections.emptyList());
fields.add(field);
}
} catch (SQLException e) {
diff --git
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDataSourceDatabase.java
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDataSourceDatabase.java
index 6348e05c..2f6b7154 100644
---
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDataSourceDatabase.java
+++
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDataSourceDatabase.java
@@ -26,22 +26,23 @@ import org.apache.arrow.adbc.core.AdbcConnection;
import org.apache.arrow.adbc.core.AdbcDatabase;
import org.apache.arrow.adbc.core.AdbcException;
import org.apache.arrow.memory.BufferAllocator;
+import org.checkerframework.checker.nullness.qual.Nullable;
/** An instance of a database based on a {@link DataSource}. */
public final class JdbcDataSourceDatabase implements AdbcDatabase {
private final BufferAllocator allocator;
private final DataSource dataSource;
- private final String username;
- private final String password;
+ private final @Nullable String username;
+ private final @Nullable String password;
private final JdbcQuirks quirks;
private final AtomicInteger counter;
- private Connection connection;
+ private @Nullable Connection connection;
JdbcDataSourceDatabase(
BufferAllocator allocator,
DataSource dataSource,
- String username,
- String password,
+ @Nullable String username,
+ @Nullable String password,
JdbcQuirks quirks)
throws AdbcException {
this.allocator = Objects.requireNonNull(allocator);
@@ -55,12 +56,13 @@ public final class JdbcDataSourceDatabase implements
AdbcDatabase {
@Override
public AdbcConnection connect() throws AdbcException {
+ @Nullable Connection conn = this.connection;
try {
- if (connection == null) {
+ if (conn == null) {
if (username != null && password != null) {
- connection = dataSource.getConnection(username, password);
+ conn = this.connection = dataSource.getConnection(username,
password);
} else {
- connection = dataSource.getConnection();
+ conn = this.connection = dataSource.getConnection();
}
}
} catch (SQLException e) {
@@ -70,7 +72,7 @@ public final class JdbcDataSourceDatabase implements
AdbcDatabase {
return new JdbcConnection(
allocator.newChildAllocator(
"adbc-jdbc-datasource-connection-" + count, 0,
allocator.getLimit()),
- connection,
+ conn,
quirks);
}
diff --git
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDriver.java
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDriver.java
index 14d4f0be..6d5fd74f 100644
---
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDriver.java
+++
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDriver.java
@@ -25,13 +25,16 @@ import org.apache.arrow.adbc.core.AdbcDriver;
import org.apache.arrow.adbc.core.AdbcException;
import org.apache.arrow.adbc.sql.SqlQuirks;
import org.apache.arrow.memory.BufferAllocator;
+import org.checkerframework.checker.nullness.qual.Nullable;
/** An ADBC driver wrapping the JDBC API. */
public class JdbcDriver implements AdbcDriver {
/** A parameter for creating an {@link AdbcDatabase} from a {@link
DataSource}. */
public static final String PARAM_DATASOURCE = "adbc.jdbc.datasource";
+
/** A parameter for specifying backend-specific configuration (type: {@link
JdbcQuirks}). */
public static final String PARAM_JDBC_QUIRKS = "adbc.jdbc.quirks";
+
/**
* A parameter for specifying a URI to connect to.
*
@@ -85,8 +88,8 @@ public class JdbcDriver implements AdbcDriver {
return new JdbcDataSourceDatabase(allocator, dataSource, username,
password, jdbcQuirks);
}
- private static <T> T getParam(Class<T> klass, Map<String, Object>
parameters, String... choices)
- throws AdbcException {
+ private static <T> @Nullable T getParam(
+ Class<T> klass, Map<String, Object> parameters, String... choices)
throws AdbcException {
Object result = null;
for (String choice : choices) {
Object value = parameters.get(choice);
diff --git
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDriverUtil.java
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDriverUtil.java
index b2b65d77..c4f39277 100644
---
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDriverUtil.java
+++
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDriverUtil.java
@@ -23,6 +23,7 @@ import java.util.HashSet;
import java.util.Set;
import org.apache.arrow.adbc.core.AdbcException;
import org.apache.arrow.adbc.core.AdbcStatusCode;
+import org.checkerframework.checker.nullness.qual.Nullable;
final class JdbcDriverUtil {
// Do our best to properly map database-specific errors to NOT_FOUND status.
@@ -41,11 +42,14 @@ final class JdbcDriverUtil {
throw new AssertionError("Do not instantiate this class");
}
- static String prefixExceptionMessage(final String s) {
+ static String prefixExceptionMessage(final @Nullable String s) {
+ if (s == null) {
+ return "[JDBC] (No or unknown error)";
+ }
return "[JDBC] " + s;
}
- static AdbcStatusCode guessStatusCode(String sqlState) {
+ static AdbcStatusCode guessStatusCode(@Nullable String sqlState) {
if (sqlState == null) {
return AdbcStatusCode.UNKNOWN;
} else if (SQLSTATE_TABLE_NOT_FOUND.contains(sqlState)) {
@@ -56,10 +60,11 @@ final class JdbcDriverUtil {
static AdbcException fromSqlException(SQLException e) {
// Unwrap an unknown exception with a known cause inside of it
- if (isUnknown(e)
- && e.getCause() instanceof SQLException
- && !isUnknown((SQLException) e.getCause())) {
- return fromSqlException((SQLException) e.getCause());
+ if (isUnknown(e)) {
+ final Throwable cause = e.getCause();
+ if (cause instanceof SQLException && !isUnknown((SQLException) cause)) {
+ return fromSqlException((SQLException) cause);
+ }
}
// Only JDBC-prefix the message if it is actually JDBC specific
diff --git
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcQuirks.java
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcQuirks.java
index 8702c06f..39a9e327 100644
---
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcQuirks.java
+++
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcQuirks.java
@@ -20,12 +20,13 @@ import java.util.Objects;
import org.apache.arrow.adapter.jdbc.JdbcToArrowUtils;
import org.apache.arrow.adbc.driver.jdbc.adapter.JdbcToArrowTypeConverter;
import org.apache.arrow.adbc.sql.SqlQuirks;
+import org.checkerframework.checker.nullness.qual.Nullable;
/** Backend-specific quirks for the JDBC adapter. */
public class JdbcQuirks {
final String backendName;
JdbcToArrowTypeConverter typeConverter;
- SqlQuirks sqlQuirks;
+ @Nullable SqlQuirks sqlQuirks;
public JdbcQuirks(String backendName) {
this.backendName = Objects.requireNonNull(backendName);
@@ -45,7 +46,7 @@ public class JdbcQuirks {
}
/** The SQL syntax quirks. */
- public SqlQuirks getSqlQuirks() {
+ public @Nullable SqlQuirks getSqlQuirks() {
return sqlQuirks;
}
diff --git
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcStatement.java
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcStatement.java
index fd39e6d0..339c31ad 100644
---
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcStatement.java
+++
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcStatement.java
@@ -22,6 +22,7 @@ import java.sql.Connection;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
@@ -36,6 +37,7 @@ import org.apache.arrow.adbc.core.AdbcException;
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.sql.SqlQuirks;
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.util.AutoCloseables;
import org.apache.arrow.vector.VectorSchemaRoot;
@@ -43,6 +45,7 @@ import org.apache.arrow.vector.ipc.ArrowReader;
import org.apache.arrow.vector.types.pojo.ArrowType;
import org.apache.arrow.vector.types.pojo.Field;
import org.apache.arrow.vector.types.pojo.Schema;
+import org.checkerframework.checker.nullness.qual.Nullable;
public class JdbcStatement implements AdbcStatement {
private final BufferAllocator allocator;
@@ -50,13 +53,13 @@ public class JdbcStatement implements AdbcStatement {
private final JdbcQuirks quirks;
// State for SQL queries
- private Statement statement;
- private String sqlQuery;
- private ArrowReader reader;
- private ResultSet resultSet;
+ private @Nullable Statement statement;
+ private @Nullable String sqlQuery;
+ private @Nullable ArrowReader reader;
+ private @Nullable ResultSet resultSet;
// State for bulk ingest
- private BulkState bulkOperation;
- private VectorSchemaRoot bindRoot;
+ private @Nullable BulkState bulkOperation;
+ private @Nullable VectorSchemaRoot bindRoot;
JdbcStatement(BufferAllocator allocator, Connection connection, JdbcQuirks
quirks) {
this.allocator = allocator;
@@ -73,7 +76,7 @@ public class JdbcStatement implements AdbcStatement {
BulkIngestMode mode) {
Objects.requireNonNull(targetTableName);
final JdbcStatement statement = new JdbcStatement(allocator, connection,
quirks);
- statement.bulkOperation = new BulkState();
+ statement.bulkOperation = new BulkState(mode, targetTableName);
statement.bulkOperation.mode = mode;
statement.bulkOperation.targetTable = targetTableName;
return statement;
@@ -93,7 +96,8 @@ public class JdbcStatement implements AdbcStatement {
bindRoot = root;
}
- private void createBulkTable() throws AdbcException {
+ private void createBulkTable(BulkState bulkOperation, VectorSchemaRoot
bindRoot)
+ throws AdbcException {
final StringBuilder create = new StringBuilder("CREATE TABLE ");
create.append(bulkOperation.targetTable);
create.append(" (");
@@ -104,7 +108,13 @@ public class JdbcStatement implements AdbcStatement {
final Field field = bindRoot.getVector(col).getField();
create.append(field.getName());
create.append(' ');
- String typeName =
quirks.getSqlQuirks().getArrowToSqlTypeNameMapping().apply(field.getType());
+ @Nullable SqlQuirks sqlQuirks = quirks.getSqlQuirks();
+ if (sqlQuirks == null) {
+ throw AdbcException.invalidState(
+ JdbcDriverUtil.prefixExceptionMessage(
+ "Must create driver with SqlQuirks to use bulk ingestion"));
+ }
+ String typeName =
sqlQuirks.getArrowToSqlTypeNameMapping().apply(field.getType());
if (typeName == null) {
throw AdbcException.notImplemented(
"[JDBC] Cannot generate CREATE TABLE statement for field " +
field);
@@ -124,13 +134,14 @@ public class JdbcStatement implements AdbcStatement {
}
}
- private UpdateResult executeBulk() throws AdbcException {
+ private UpdateResult executeBulk(BulkState bulkOperation) throws
AdbcException {
if (bindRoot == null) {
throw AdbcException.invalidState("[JDBC] Must call bind() before bulk
insert");
}
+ VectorSchemaRoot bind = bindRoot;
if (bulkOperation.mode == BulkIngestMode.CREATE) {
- createBulkTable();
+ createBulkTable(bulkOperation, bind);
}
// XXX: potential injection
@@ -138,7 +149,7 @@ public class JdbcStatement implements AdbcStatement {
final StringBuilder insert = new StringBuilder("INSERT INTO ");
insert.append(bulkOperation.targetTable);
insert.append(" VALUES (");
- for (int col = 0; col < bindRoot.getFieldVectors().size(); col++) {
+ for (int col = 0; col < bind.getFieldVectors().size(); col++) {
if (col > 0) {
insert.append(", ");
}
@@ -156,7 +167,7 @@ public class JdbcStatement implements AdbcStatement {
try {
try {
final JdbcParameterBinder binder =
- JdbcParameterBinder.builder(statement, bindRoot).bindAll().build();
+ JdbcParameterBinder.builder(statement, bind).bindAll().build();
statement.clearBatch();
while (binder.next()) {
statement.addBatch();
@@ -168,7 +179,7 @@ public class JdbcStatement implements AdbcStatement {
} catch (SQLException e) {
throw JdbcDriverUtil.fromSqlException(e);
}
- return new UpdateResult(bindRoot.getRowCount());
+ return new UpdateResult(bind.getRowCount());
}
private void invalidatePriorQuery() throws AdbcException {
@@ -202,10 +213,12 @@ public class JdbcStatement implements AdbcStatement {
@Override
public UpdateResult executeUpdate() throws AdbcException {
if (bulkOperation != null) {
- return executeBulk();
+ return executeBulk(bulkOperation);
} else if (sqlQuery == null) {
throw AdbcException.invalidState("[JDBC] Must setSqlQuery() first");
}
+ String query = sqlQuery;
+
long affectedRows = 0;
try {
invalidatePriorQuery();
@@ -226,7 +239,7 @@ public class JdbcStatement implements AdbcStatement {
statement =
connection.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
- affectedRows = statement.executeUpdate(sqlQuery);
+ affectedRows = statement.executeUpdate(query);
}
} catch (SQLException e) {
throw JdbcDriverUtil.fromSqlException(e);
@@ -241,6 +254,8 @@ public class JdbcStatement implements AdbcStatement {
} else if (sqlQuery == null) {
throw AdbcException.invalidState("[JDBC] Must setSqlQuery() first");
}
+ String query = sqlQuery;
+
try {
invalidatePriorQuery();
if (statement instanceof PreparedStatement) {
@@ -255,13 +270,13 @@ public class JdbcStatement implements AdbcStatement {
statement =
connection.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
- resultSet = statement.executeQuery(sqlQuery);
+ resultSet = statement.executeQuery(query);
reader = new JdbcArrowReader(allocator, resultSet, /*overrideSchema*/
null);
}
} catch (SQLException e) {
throw JdbcDriverUtil.fromSqlException(e);
}
- return new QueryResult(/*affectedRows=*/ -1, reader);
+ return new QueryResult(/* affectedRows */ -1, reader);
}
@Override
@@ -271,6 +286,8 @@ public class JdbcStatement implements AdbcStatement {
} else if (sqlQuery == null) {
throw AdbcException.invalidState("[JDBC] Must setSqlQuery() first");
}
+ String query = sqlQuery;
+
try {
invalidatePriorQuery();
final PreparedStatement preparedStatement;
@@ -283,13 +300,21 @@ public class JdbcStatement implements AdbcStatement {
ownedStatement = null;
} else {
// new statement
- preparedStatement = connection.prepareStatement(sqlQuery);
+ preparedStatement = connection.prepareStatement(query);
ownedStatement = preparedStatement;
}
final JdbcToArrowConfig config =
JdbcArrowReader.makeJdbcConfig(allocator);
- final Schema schema =
- JdbcToArrowUtils.jdbcToArrowSchema(preparedStatement.getMetaData(),
config);
+ @Nullable ResultSetMetaData rsmd = preparedStatement.getMetaData();
+ if (rsmd == null) {
+ throw new AdbcException(
+ JdbcDriverUtil.prefixExceptionMessage("JDBC driver returned null
ResultSetMetaData"),
+ /*cause*/ null,
+ AdbcStatusCode.INTERNAL,
+ null,
+ 0);
+ }
+ final Schema schema = JdbcToArrowUtils.jdbcToArrowSchema(rsmd, config);
if (ownedStatement != null) {
ownedStatement.close();
}
@@ -332,12 +357,14 @@ public class JdbcStatement implements AdbcStatement {
if (sqlQuery == null) {
throw AdbcException.invalidArgument("[JDBC] Must setSqlQuery(String)
before prepare()");
}
+ String query = sqlQuery;
if (resultSet != null) {
resultSet.close();
}
+
statement =
connection.prepareStatement(
- sqlQuery, ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
+ query, ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
} catch (SQLException e) {
throw JdbcDriverUtil.fromSqlException(e);
}
@@ -349,7 +376,12 @@ public class JdbcStatement implements AdbcStatement {
}
private static final class BulkState {
- public BulkIngestMode mode;
+ BulkIngestMode mode;
String targetTable;
+
+ BulkState(BulkIngestMode mode, String targetTable) {
+ this.mode = mode;
+ this.targetTable = targetTable;
+ }
}
}
diff --git
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/ObjectMetadataBuilder.java
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/ObjectMetadataBuilder.java
index 909aec59..3fefc483 100644
---
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/ObjectMetadataBuilder.java
+++
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/ObjectMetadataBuilder.java
@@ -29,8 +29,9 @@ import java.util.Map;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import org.apache.arrow.adbc.core.AdbcConnection;
+import org.apache.arrow.adbc.core.AdbcException;
+import org.apache.arrow.adbc.core.AdbcStatusCode;
import org.apache.arrow.adbc.core.StandardSchemas;
-import org.apache.arrow.memory.ArrowBuf;
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.util.AutoCloseables;
import org.apache.arrow.vector.IntVector;
@@ -43,6 +44,7 @@ import org.apache.arrow.vector.complex.impl.UnionListWriter;
import org.apache.arrow.vector.complex.writer.BaseWriter.ListWriter;
import org.apache.arrow.vector.complex.writer.BaseWriter.StructWriter;
import org.apache.arrow.vector.complex.writer.VarCharWriter;
+import org.checkerframework.checker.nullness.qual.Nullable;
/** Helper class to track state needed to build up the object metadata
structure. */
final class ObjectMetadataBuilder implements AutoCloseable {
@@ -139,11 +141,19 @@ final class ObjectMetadataBuilder implements
AutoCloseable {
this.constraintColumnUsageStructWriter.varChar("fk_column_name");
}
- VectorSchemaRoot build() throws SQLException {
+ VectorSchemaRoot build() throws AdbcException, SQLException {
try (final ResultSet rs = dbmd.getCatalogs()) {
int catalogCount = 0;
while (rs.next()) {
final String catalogName = rs.getString(1);
+ if (catalogName == null) {
+ throw new AdbcException(
+ JdbcDriverUtil.prefixExceptionMessage("JDBC driver returned null
catalog name"),
+ null,
+ AdbcStatusCode.INVALID_DATA,
+ null,
+ 0);
+ }
if (!catalogPattern.test(catalogName)) continue;
addCatalogRow(catalogCount, catalogName);
catalogCount++;
@@ -156,11 +166,16 @@ final class ObjectMetadataBuilder implements
AutoCloseable {
root.setRowCount(catalogCount);
}
VectorSchemaRoot result = root;
- root = null;
+ try {
+ root = VectorSchemaRoot.create(StandardSchemas.GET_OBJECTS_SCHEMA,
allocator);
+ } catch (RuntimeException e) {
+ result.close();
+ throw e;
+ }
return result;
}
- private void addCatalogRow(int rowIndex, String catalogName) throws
SQLException {
+ private void addCatalogRow(int rowIndex, String catalogName) throws
AdbcException, SQLException {
catalogNames.setSafe(rowIndex,
catalogName.getBytes(StandardCharsets.UTF_8));
if (depth == AdbcConnection.GetObjectsDepth.CATALOGS) {
catalogDbSchemas.setNull(rowIndex);
@@ -171,12 +186,20 @@ final class ObjectMetadataBuilder implements
AutoCloseable {
}
}
- private int buildDbSchemas(int rowIndex, String catalogName) throws
SQLException {
+ private int buildDbSchemas(int rowIndex, String catalogName) throws
AdbcException, SQLException {
int dbSchemaCount = 0;
// TODO: get tables with no schema
try (final ResultSet rs = dbmd.getSchemas(catalogName, dbSchemaPattern)) {
while (rs.next()) {
final String dbSchemaName = rs.getString(1);
+ if (dbSchemaName == null) {
+ throw new AdbcException(
+ JdbcDriverUtil.prefixExceptionMessage("JDBC driver returned null
schema name"),
+ null,
+ AdbcStatusCode.INVALID_DATA,
+ null,
+ 0);
+ }
addDbSchemaRow(rowIndex + dbSchemaCount, catalogName, dbSchemaName);
dbSchemaCount++;
}
@@ -185,7 +208,7 @@ final class ObjectMetadataBuilder implements AutoCloseable {
}
private void addDbSchemaRow(int rowIndex, String catalogName, String
dbSchemaName)
- throws SQLException {
+ throws AdbcException, SQLException {
dbSchemas.setIndexDefined(rowIndex);
dbSchemaNames.setSafe(rowIndex,
dbSchemaName.getBytes(StandardCharsets.UTF_8));
if (depth == AdbcConnection.GetObjectsDepth.DB_SCHEMAS) {
@@ -198,14 +221,23 @@ final class ObjectMetadataBuilder implements
AutoCloseable {
}
private int buildTables(int rowIndex, String catalogName, String
dbSchemaName)
- throws SQLException {
+ throws AdbcException, SQLException {
int tableCount = 0;
try (final ResultSet rs =
dbmd.getTables(catalogName, dbSchemaName, tableNamePattern,
tableTypesFilter)) {
while (rs.next()) {
- final String tableName = rs.getString(3);
- final String tableType = rs.getString(4);
+ final @Nullable String tableName = rs.getString(3);
+ final @Nullable String tableType = rs.getString(4);
+ if (tableName == null || tableType == null) {
+ throw new AdbcException(
+ JdbcDriverUtil.prefixExceptionMessage("JDBC driver returned null
table name/type"),
+ null,
+ AdbcStatusCode.INTERNAL,
+ null,
+ 0);
+ }
+
tables.setIndexDefined(rowIndex + tableCount);
tableNames.setSafe(rowIndex + tableCount,
tableName.getBytes(StandardCharsets.UTF_8));
tableTypes.setSafe(rowIndex + tableCount,
tableType.getBytes(StandardCharsets.UTF_8));
@@ -216,7 +248,7 @@ final class ObjectMetadataBuilder implements AutoCloseable {
// 1. Primary keys
try (final ResultSet pk = dbmd.getPrimaryKeys(catalogName,
dbSchemaName, tableName)) {
String constraintName = null;
- List<String> constraintColumns = new ArrayList<>();
+ List<@Nullable String> constraintColumns = new ArrayList<>();
while (pk.next()) {
constraintName = pk.getString(6);
String columnName = pk.getString(4);
@@ -232,8 +264,8 @@ final class ObjectMetadataBuilder implements AutoCloseable {
// 2. Foreign keys ("imported" keys)
try (final ResultSet fk = dbmd.getImportedKeys(catalogName,
dbSchemaName, tableName)) {
- List<String> names = new ArrayList<>();
- List<List<String>> columns = new ArrayList<>();
+ List<@Nullable String> names = new ArrayList<>();
+ List<List<@Nullable String>> columns = new ArrayList<>();
List<List<ReferencedColumn>> references = new ArrayList<>();
while (fk.next()) {
String keyName = fk.getString(12);
@@ -245,11 +277,19 @@ final class ObjectMetadataBuilder implements
AutoCloseable {
references.add(new ArrayList<>());
}
columns.get(columns.size() - 1).add(keyColumn);
- final ReferencedColumn reference = new ReferencedColumn();
- reference.catalog = fk.getString(1);
- reference.dbSchema = fk.getString(2);
- reference.table = fk.getString(3);
- reference.column = fk.getString(4);
+ final @Nullable String fkTableName = fk.getString(3);
+ final @Nullable String fkColumnName = fk.getString(4);
+ if (fkTableName == null || fkColumnName == null) {
+ throw new AdbcException(
+ JdbcDriverUtil.prefixExceptionMessage(
+ "JDBC driver returned null table/column name"),
+ null,
+ AdbcStatusCode.INTERNAL,
+ null,
+ 0);
+ }
+ final ReferencedColumn reference =
+ new ReferencedColumn(fk.getString(1), fk.getString(2),
fkTableName, fkColumnName);
references.get(references.size() - 1).add(reference);
}
@@ -261,16 +301,26 @@ final class ObjectMetadataBuilder implements
AutoCloseable {
// 3. UNIQUE constraints
try (final ResultSet uq =
dbmd.getIndexInfo(catalogName, dbSchemaName, tableName, true,
false)) {
- Map<String, ArrayList<String>> uniqueConstraints = new HashMap<>();
+ Map<String, ArrayList<@Nullable String>> uniqueConstraints = new
HashMap<>();
while (uq.next()) {
- String constraintName = uq.getString(6);
- String columnName = uq.getString(9);
+ @Nullable String constraintName = uq.getString(6);
+ @Nullable String columnName = uq.getString(9);
int columnIndex = uq.getInt(8);
+ if (constraintName == null || columnName == null) {
+ throw new AdbcException(
+ JdbcDriverUtil.prefixExceptionMessage(
+ "JDBC driver returned null constraint/column name"),
+ null,
+ AdbcStatusCode.INTERNAL,
+ null,
+ 0);
+ }
+
if (!uniqueConstraints.containsKey(constraintName)) {
uniqueConstraints.put(constraintName, new ArrayList<>());
}
- ArrayList<String> uniqueColumns =
uniqueConstraints.get(constraintName);
+ ArrayList<@Nullable String> uniqueColumns =
uniqueConstraints.get(constraintName);
while (uniqueColumns.size() < columnIndex) uniqueColumns.add(null);
uniqueColumns.set(columnIndex - 1, columnName);
}
@@ -299,14 +349,22 @@ final class ObjectMetadataBuilder implements
AutoCloseable {
}
private int buildColumns(int rowIndex, String catalogName, String
dbSchemaName, String tableName)
- throws SQLException {
+ throws AdbcException, SQLException {
int columnCount = 0;
try (final ResultSet rs =
dbmd.getColumns(catalogName, dbSchemaName, tableName,
columnNamePattern)) {
while (rs.next()) {
- final String columnName = rs.getString(4);
+ final @Nullable String columnName = rs.getString(4);
+ if (columnName == null) {
+ throw new AdbcException(
+ JdbcDriverUtil.prefixExceptionMessage("JDBC driver returned null
column name"),
+ null,
+ AdbcStatusCode.INTERNAL,
+ null,
+ 0);
+ }
final int ordinalPosition = rs.getInt(17);
- final String remarks = rs.getString(12);
+ final @Nullable String remarks = rs.getString(12);
final int xdbcDataType = rs.getInt(5);
// TODO: other JDBC metadata
@@ -324,27 +382,28 @@ final class ObjectMetadataBuilder implements
AutoCloseable {
return columnCount;
}
- private void writeVarChar(VarCharWriter writer, String value) {
- byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
- try (ArrowBuf tempBuf = allocator.buffer(bytes.length)) {
- tempBuf.setBytes(0, bytes, 0, bytes.length);
- writer.writeVarChar(0, bytes.length, tempBuf);
- }
- }
-
private void addConstraint(
- String constraintName,
+ @Nullable String constraintName,
String constraintType,
- List<String> constraintColumns,
+ List<@Nullable String> constraintColumns,
List<ReferencedColumn> referencedColumns) {
tableConstraintsStructWriter.start();
- writeVarChar(this.constraintNamesWriter, constraintName);
- writeVarChar(this.constraintTypesWriter, constraintType);
+ if (constraintName == null) {
+ this.constraintNamesWriter.writeNull();
+ } else {
+ this.constraintNamesWriter.writeVarChar(constraintName);
+ }
+ this.constraintTypesWriter.writeVarChar(constraintType);
constraintColumnNamesWriter.startList();
- for (final String constraintColumn : constraintColumns) {
- writeVarChar(constraintColumnNamesWriter.varChar(), constraintColumn);
+ for (final @Nullable String constraintColumn : constraintColumns) {
+ VarCharWriter writer = constraintColumnNamesWriter.varChar();
+ if (constraintColumn == null) {
+ writer.writeNull();
+ } else {
+ writer.writeVarChar(constraintColumn);
+ }
}
constraintColumnNamesWriter.endList();
@@ -352,11 +411,17 @@ final class ObjectMetadataBuilder implements
AutoCloseable {
for (ReferencedColumn referencedColumn : referencedColumns) {
constraintColumnUsageStructWriter.start();
if (referencedColumn.catalog != null) {
- writeVarChar(constraintColumnUsageFkCatalogsWriter,
referencedColumn.catalog);
+
constraintColumnUsageFkCatalogsWriter.writeVarChar(referencedColumn.catalog);
+ } else {
+ constraintColumnUsageFkCatalogsWriter.writeNull();
+ }
+ if (referencedColumn.dbSchema != null) {
+
constraintColumnUsageFkDbSchemasWriter.writeVarChar(referencedColumn.dbSchema);
+ } else {
+ constraintColumnUsageFkDbSchemasWriter.writeNull();
}
- writeVarChar(constraintColumnUsageFkDbSchemasWriter,
referencedColumn.dbSchema);
- writeVarChar(constraintColumnUsageFkTablesWriter,
referencedColumn.table);
- writeVarChar(constraintColumnUsageFkColumnsWriter,
referencedColumn.column);
+ constraintColumnUsageFkTablesWriter.writeVarChar(referencedColumn.table);
+
constraintColumnUsageFkColumnsWriter.writeVarChar(referencedColumn.column);
constraintColumnUsageStructWriter.end();
}
constraintColumnUsageWriter.endList();
@@ -370,7 +435,7 @@ final class ObjectMetadataBuilder implements AutoCloseable {
}
/** Turn a SQL-style pattern (%, _) to a regex. */
- String translatePattern(String filter) {
+ static String translatePattern(String filter) {
StringBuilder builder = new StringBuilder(filter.length());
builder.append("^");
for (char c : filter.toCharArray()) {
@@ -387,9 +452,26 @@ final class ObjectMetadataBuilder implements AutoCloseable
{
}
static class ReferencedColumn {
- String catalog;
- String dbSchema;
+ @Nullable String catalog;
+ @Nullable String dbSchema;
String table;
String column;
+
+ public ReferencedColumn(
+ @Nullable String catalog, @Nullable String dbSchema, String table,
String column)
+ throws AdbcException {
+ this.catalog = catalog;
+ this.dbSchema = dbSchema;
+ if (table == null || column == null) {
+ throw new AdbcException(
+ JdbcDriverUtil.prefixExceptionMessage("JDBC driver returned null
table/column name"),
+ null,
+ AdbcStatusCode.INTERNAL,
+ null,
+ 0);
+ }
+ this.table = table;
+ this.column = column;
+ }
}
}
diff --git
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/StandardJdbcQuirks.java
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/StandardJdbcQuirks.java
index e87283cb..d4c55566 100644
---
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/StandardJdbcQuirks.java
+++
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/StandardJdbcQuirks.java
@@ -16,17 +16,15 @@
*/
package org.apache.arrow.adbc.driver.jdbc;
-import java.sql.Types;
-import org.apache.arrow.adapter.jdbc.JdbcToArrowUtils;
-import org.apache.arrow.adbc.driver.jdbc.adapter.JdbcFieldInfoExtra;
+import org.apache.arrow.adbc.driver.jdbc.adapter.JdbcToArrowTypeConverters;
import org.apache.arrow.adbc.sql.SqlQuirks;
-import org.apache.arrow.vector.types.TimeUnit;
-import org.apache.arrow.vector.types.Types.MinorType;
import org.apache.arrow.vector.types.pojo.ArrowType;
public final class StandardJdbcQuirks {
public static final JdbcQuirks MS_SQL_SERVER =
- JdbcQuirks.builder("Microsoft SQL
Server").typeConverter(StandardJdbcQuirks::mssql).build();
+ JdbcQuirks.builder("Microsoft SQL Server")
+ .typeConverter(JdbcToArrowTypeConverters.MICROSOFT_SQL_SERVER)
+ .build();
public static final JdbcQuirks POSTGRESQL =
JdbcQuirks.builder("PostgreSQL")
.sqlQuirks(
@@ -40,41 +38,6 @@ public final class StandardJdbcQuirks {
arrowType);
}))
.build())
- .typeConverter(StandardJdbcQuirks::postgresql)
+ .typeConverter(JdbcToArrowTypeConverters.POSTGRESQL)
.build();
- private static final int MS_SQL_TYPE_DATETIMEOFFSET = -155;
-
- private static ArrowType mssql(JdbcFieldInfoExtra field) {
- switch (field.getJdbcType()) {
- case Types.TIME:
- return MinorType.TIMENANO.getType();
- case Types.TIMESTAMP:
- // DATETIME2
- // Precision is "100 nanoseconds" -> TimeUnit is NANOSECOND
- return MinorType.TIMESTAMPNANO.getType();
- case MS_SQL_TYPE_DATETIMEOFFSET:
- // DATETIMEOFFSET
- // Precision is "100 nanoseconds" -> TimeUnit is NANOSECOND
- return new ArrowType.Timestamp(TimeUnit.NANOSECOND, "UTC");
- default:
- return JdbcToArrowUtils.getArrowTypeFromJdbcType(field.getFieldInfo(),
/*calendar*/ null);
- }
- }
-
- private static ArrowType postgresql(JdbcFieldInfoExtra field) {
- switch (field.getJdbcType()) {
- case Types.TIME:
- return MinorType.TIMEMICRO.getType();
- case Types.TIMESTAMP:
- if ("timestamptz".equals(field.getTypeName())) {
- return new ArrowType.Timestamp(TimeUnit.MICROSECOND, "UTC");
- } else if ("timestamp".equals(field.getTypeName())) {
- return MinorType.TIMESTAMPMICRO.getType();
- }
- // Unknown type
- return null;
- default:
- return JdbcToArrowUtils.getArrowTypeFromJdbcType(field.getFieldInfo(),
/*calendar*/ null);
- }
- }
}
diff --git
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/UrlDataSource.java
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/UrlDataSource.java
index f74b97f4..51266ca0 100644
---
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/UrlDataSource.java
+++
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/UrlDataSource.java
@@ -25,15 +25,17 @@ import java.sql.SQLFeatureNotSupportedException;
import java.util.Objects;
import java.util.logging.Logger;
import javax.sql.DataSource;
+import org.checkerframework.checker.nullness.qual.Nullable;
/** Adapt a JDBC URL to the DataSource interface. */
class UrlDataSource implements DataSource {
final String target;
- PrintWriter logWriter;
+ @Nullable PrintWriter logWriter;
int loginTimeout;
UrlDataSource(String target) {
this.target = Objects.requireNonNull(target);
+ this.logWriter = null;
}
@Override
@@ -47,7 +49,8 @@ class UrlDataSource implements DataSource {
}
@Override
- public PrintWriter getLogWriter() throws SQLException {
+ @SuppressWarnings("override.return")
+ public @Nullable PrintWriter getLogWriter() throws SQLException {
return logWriter;
}
diff --git
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/adapter/JdbcFieldInfoExtra.java
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/adapter/JdbcFieldInfoExtra.java
index b6741a7c..a1e4080f 100644
---
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/adapter/JdbcFieldInfoExtra.java
+++
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/adapter/JdbcFieldInfoExtra.java
@@ -20,6 +20,7 @@ package org.apache.arrow.adbc.driver.jdbc.adapter;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.arrow.adapter.jdbc.JdbcFieldInfo;
+import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Information about a column from JDBC for inferring column type.
@@ -31,8 +32,8 @@ public final class JdbcFieldInfoExtra {
final JdbcFieldInfo info;
final String typeName;
final int numPrecRadix;
- final String remarks;
- final String columnDef;
+ final @Nullable String remarks;
+ final @Nullable String columnDef;
final int sqlDataType;
final int sqlDatetimeSub;
final int charOctetLength;
@@ -46,7 +47,11 @@ public final class JdbcFieldInfoExtra {
*/
public JdbcFieldInfoExtra(ResultSet rs) throws SQLException {
final int dataType = rs.getInt("DATA_TYPE");
- this.typeName = rs.getString("TYPE_NAME");
+ final @Nullable String maybeTypeName = rs.getString("TYPE_NAME");
+ if (maybeTypeName == null) {
+ throw new RuntimeException("Field " + "TYPE_NAME" + " was null");
+ }
+ this.typeName = maybeTypeName;
final int columnSize = rs.getInt("COLUMN_SIZE");
final int decimalDigits = rs.getInt("DECIMAL_DIGITS");
this.numPrecRadix = rs.getInt("NUM_PREC_RADIX");
@@ -79,11 +84,11 @@ public final class JdbcFieldInfoExtra {
return numPrecRadix;
}
- public String getRemarks() {
+ public @Nullable String getRemarks() {
return remarks;
}
- public String getColumnDef() {
+ public @Nullable String getColumnDef() {
return columnDef;
}
diff --git
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/adapter/JdbcToArrowTypeConverters.java
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/adapter/JdbcToArrowTypeConverters.java
index 928bfdf8..ba8b6fb3 100644
---
a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/adapter/JdbcToArrowTypeConverters.java
+++
b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/adapter/JdbcToArrowTypeConverters.java
@@ -20,22 +20,26 @@ package org.apache.arrow.adbc.driver.jdbc.adapter;
import java.sql.Types;
import org.apache.arrow.adapter.jdbc.JdbcToArrowUtils;
import org.apache.arrow.vector.types.TimeUnit;
+import org.apache.arrow.vector.types.Types.MinorType;
import org.apache.arrow.vector.types.pojo.ArrowType;
public final class JdbcToArrowTypeConverters {
public static final JdbcToArrowTypeConverter MICROSOFT_SQL_SERVER =
JdbcToArrowTypeConverters::mssql;
public static final JdbcToArrowTypeConverter POSTGRESQL =
JdbcToArrowTypeConverters::postgresql;
+ private static final int MS_SQL_TYPE_DATETIMEOFFSET = -155;
private static ArrowType mssql(JdbcFieldInfoExtra field) {
switch (field.getJdbcType()) {
+ case Types.TIME:
+ return MinorType.TIMENANO.getType();
+ case Types.TIMESTAMP:
// DATETIME2
// Precision is "100 nanoseconds" -> TimeUnit is NANOSECOND
- case Types.TIMESTAMP:
- return new ArrowType.Timestamp(TimeUnit.NANOSECOND, /*timezone*/ null);
+ return MinorType.TIMESTAMPNANO.getType();
+ case MS_SQL_TYPE_DATETIMEOFFSET:
// DATETIMEOFFSET
// Precision is "100 nanoseconds" -> TimeUnit is NANOSECOND
- case -155:
return new ArrowType.Timestamp(TimeUnit.NANOSECOND, "UTC");
default:
return JdbcToArrowUtils.getArrowTypeFromJdbcType(field.getFieldInfo(),
/*calendar*/ null);
@@ -44,6 +48,8 @@ public final class JdbcToArrowTypeConverters {
private static ArrowType postgresql(JdbcFieldInfoExtra field) {
switch (field.getJdbcType()) {
+ case Types.TIME:
+ return MinorType.TIMEMICRO.getType();
case Types.TIMESTAMP:
{
int decimalDigits = field.getScale();
@@ -58,7 +64,8 @@ public final class JdbcToArrowTypeConverters {
unit = TimeUnit.NANOSECOND;
} else {
// Negative precision?
- return null;
+ throw new UnsupportedOperationException(
+ "Cannot convert type to Arrow Timestamp (precision is
negative)");
}
if ("timestamptz".equals(field.getTypeName())) {
return new ArrowType.Timestamp(unit, "UTC");
@@ -66,7 +73,7 @@ public final class JdbcToArrowTypeConverters {
return new ArrowType.Timestamp(unit, /*timezone*/ null);
}
// Unknown type
- return null;
+ throw new UnsupportedOperationException("Cannot convert type to
Arrow Timestamp");
}
default:
return JdbcToArrowUtils.getArrowTypeFromJdbcType(field.getFieldInfo(),
/*calendar*/ null);
diff --git
a/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/AbstractConnectionMetadataTest.java
b/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/AbstractConnectionMetadataTest.java
index 6a5fc905..f31588e6 100644
---
a/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/AbstractConnectionMetadataTest.java
+++
b/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/AbstractConnectionMetadataTest.java
@@ -323,8 +323,7 @@ public abstract class AbstractConnectionMetadataTest {
final Schema schema =
new Schema(
Arrays.asList(
- Field.nullable(
- quirks.caseFoldColumnName("INTS"), new ArrowType.Int(32,
/*signed=*/ true)),
+ Field.nullable(quirks.caseFoldColumnName("INTS"), new
ArrowType.Int(32, true)),
Field.nullable(quirks.caseFoldColumnName("STRS"), new
ArrowType.Utf8())));
try (final VectorSchemaRoot root = VectorSchemaRoot.create(schema,
allocator);
final AdbcStatement stmt = connection.bulkIngest(tableName,
BulkIngestMode.CREATE)) {
diff --git
a/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/AbstractTransactionTest.java
b/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/AbstractTransactionTest.java
index 29265ba7..e6aaa6e6 100644
---
a/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/AbstractTransactionTest.java
+++
b/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/AbstractTransactionTest.java
@@ -85,9 +85,7 @@ public abstract class AbstractTransactionTest {
@Test
void rollback() throws Exception {
final Schema schema =
- new Schema(
- Collections.singletonList(
- Field.nullable("ints", new ArrowType.Int(32, /*signed=*/
true))));
+ new Schema(Collections.singletonList(Field.nullable("ints", new
ArrowType.Int(32, true))));
connection.setAutoCommit(false);
try (VectorSchemaRoot root = VectorSchemaRoot.create(schema, allocator)) {
@@ -116,9 +114,7 @@ public abstract class AbstractTransactionTest {
@Test
void commit() throws Exception {
final Schema schema =
- new Schema(
- Collections.singletonList(
- Field.nullable("ints", new ArrowType.Int(32, /*signed=*/
true))));
+ new Schema(Collections.singletonList(Field.nullable("ints", new
ArrowType.Int(32, true))));
final String tableName = quirks.caseFoldTableName("temptable");
connection.setAutoCommit(false);
@@ -149,9 +145,7 @@ public abstract class AbstractTransactionTest {
@Test
void enableAutoCommitAlsoCommits() throws Exception {
final Schema schema =
- new Schema(
- Collections.singletonList(
- Field.nullable("ints", new ArrowType.Int(32, /*signed=*/
true))));
+ new Schema(Collections.singletonList(Field.nullable("ints", new
ArrowType.Int(32, true))));
final String tableName = quirks.caseFoldTableName("temptable");
connection.setAutoCommit(false);
diff --git
a/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/SqlTestUtil.java
b/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/SqlTestUtil.java
index 814d0a81..c0536e5c 100644
---
a/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/SqlTestUtil.java
+++
b/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/SqlTestUtil.java
@@ -79,8 +79,7 @@ public final class SqlTestUtil {
final Schema schema =
new Schema(
Arrays.asList(
- Field.notNullable(
- quirks.caseFoldColumnName("INTS"), new ArrowType.Int(32,
/*signed=*/ true)),
+ Field.notNullable(quirks.caseFoldColumnName("INTS"), new
ArrowType.Int(32, true)),
Field.nullable(quirks.caseFoldColumnName("INTS2"), new
ArrowType.Int(32, true))));
try (final VectorSchemaRoot root = VectorSchemaRoot.create(schema,
allocator)) {
final IntVector ints = (IntVector) root.getVector(0);
@@ -133,8 +132,7 @@ public final class SqlTestUtil {
new Schema(
Collections.singletonList(
Field.notNullable(
- quirks.caseFoldColumnName("PRODUCT_ID"),
- new ArrowType.Int(32, /*signed=*/ true))));
+ quirks.caseFoldColumnName("PRODUCT_ID"), new
ArrowType.Int(32, true))));
final Schema dependentSchema =
new Schema(
diff --git a/java/pom.xml b/java/pom.xml
index 5b02f67f..5521bb30 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -135,6 +135,13 @@
<version>${adbc.version}</version>
</dependency>
+ <!-- Linting and static analysis -->
+ <dependency>
+ <groupId>org.checkerframework</groupId>
+ <artifactId>checker-qual</artifactId>
+ <version>3.42.0</version>
+ </dependency>
+
<!-- Testing -->
<dependency>
<groupId>org.assertj</groupId>
@@ -279,15 +286,26 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
- <version>3.10.1</version>
+ <version>3.11.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
- <compilerArgs>
+ <encoding>UTF-8</encoding>
+ <compilerArgs combine.children="append">
<arg>-XDcompilePolicy=simple</arg>
<arg>-Xplugin:ErrorProne -Xep:NullAway:ERROR
-XepOpt:NullAway:AnnotatedPackages=com.uber</arg>
+
+ <arg>-Xmaxerrs</arg> <!-- javac only reports the first 100
errors or warnings -->
+ <arg>10000</arg>
+ <arg>-Xmaxwarns</arg>
+ <arg>10000</arg>
+ <arg>-AskipDefs=.*Test</arg> <!-- Skip analysis for Testing
classes -->
+ <arg>-AatfDoNotCache</arg> <!-- not cache results -->
+ <arg>-AprintVerboseGenerics</arg>
+ <arg>-AprintAllQualifiers</arg>
+ <arg>-Astubs=.checker-framework/:stubs</arg>
</compilerArgs>
- <annotationProcessorPaths>
+ <annotationProcessorPaths combine.children="append">
<path>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
@@ -298,7 +316,15 @@
<artifactId>nullaway</artifactId>
<version>0.10.10</version>
</path>
+ <path>
+ <groupId>org.checkerframework</groupId>
+ <artifactId>checker</artifactId>
+ <version>3.42.0</version>
+ </path>
</annotationProcessorPaths>
+ <annotationProcessors>
+
<annotationProcessor>org.checkerframework.checker.nullness.NullnessChecker</annotationProcessor>
+ </annotationProcessors>
</configuration>
</plugin>
</plugins>
diff --git a/java/sql/pom.xml b/java/sql/pom.xml
index 98942109..37904359 100644
--- a/java/sql/pom.xml
+++ b/java/sql/pom.xml
@@ -28,6 +28,12 @@
<artifactId>arrow-vector</artifactId>
</dependency>
+ <!-- Static analysis and linting -->
+ <dependency>
+ <groupId>org.checkerframework</groupId>
+ <artifactId>checker-qual</artifactId>
+ </dependency>
+
<!-- Testing -->
<dependency>
<groupId>org.assertj</groupId>
diff --git a/java/sql/src/main/java/org/apache/arrow/adbc/sql/SqlQuirks.java
b/java/sql/src/main/java/org/apache/arrow/adbc/sql/SqlQuirks.java
index 007f699c..fc864354 100644
--- a/java/sql/src/main/java/org/apache/arrow/adbc/sql/SqlQuirks.java
+++ b/java/sql/src/main/java/org/apache/arrow/adbc/sql/SqlQuirks.java
@@ -19,51 +19,53 @@ package org.apache.arrow.adbc.sql;
import java.util.function.Function;
import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.checkerframework.checker.nullness.qual.Nullable;
/** Parameters to pass to SQL-based drivers to account for
driver/vendor-specific SQL quirks. */
public final class SqlQuirks {
- public static final Function<ArrowType, String>
DEFAULT_ARROW_TYPE_TO_SQL_TYPE_NAME_MAPPING =
- (arrowType) -> {
- switch (arrowType.getTypeID()) {
- case Null:
- case Struct:
- case List:
- case LargeList:
- case FixedSizeList:
- case Union:
- case Map:
- return null;
- case Int:
- // TODO:
- return "INT";
- case FloatingPoint:
- return null;
- case Utf8:
- return "CLOB";
- case LargeUtf8:
- case Binary:
- case LargeBinary:
- case FixedSizeBinary:
- case Bool:
- case Decimal:
- case Date:
- case Time:
- case Timestamp:
- case Interval:
- case Duration:
- case NONE:
- default:
- return null;
- }
- };
- Function<ArrowType, String> arrowToSqlTypeNameMapping;
+ public static final Function<ArrowType, @Nullable String>
+ DEFAULT_ARROW_TYPE_TO_SQL_TYPE_NAME_MAPPING =
+ (arrowType) -> {
+ switch (arrowType.getTypeID()) {
+ case Null:
+ case Struct:
+ case List:
+ case LargeList:
+ case FixedSizeList:
+ case Union:
+ case Map:
+ return null;
+ case Int:
+ // TODO:
+ return "INT";
+ case FloatingPoint:
+ return null;
+ case Utf8:
+ return "CLOB";
+ case LargeUtf8:
+ case Binary:
+ case LargeBinary:
+ case FixedSizeBinary:
+ case Bool:
+ case Decimal:
+ case Date:
+ case Time:
+ case Timestamp:
+ case Interval:
+ case Duration:
+ case NONE:
+ default:
+ return null;
+ }
+ };
+ Function<ArrowType, @Nullable String> arrowToSqlTypeNameMapping;
public SqlQuirks() {
this.arrowToSqlTypeNameMapping =
DEFAULT_ARROW_TYPE_TO_SQL_TYPE_NAME_MAPPING;
}
/** The mapping from Arrow type to SQL type name, used to build queries. */
- public Function<ArrowType, String> getArrowToSqlTypeNameMapping() {
+ public Function<ArrowType, @Nullable String> getArrowToSqlTypeNameMapping() {
return arrowToSqlTypeNameMapping;
}
@@ -73,9 +75,10 @@ public final class SqlQuirks {
}
public static final class Builder {
- Function<ArrowType, String> arrowToSqlTypeNameMapping;
+ @Nullable Function<ArrowType, @Nullable String> arrowToSqlTypeNameMapping;
- public Builder arrowToSqlTypeNameMapping(Function<ArrowType, String>
mapper) {
+ public Builder arrowToSqlTypeNameMapping(
+ @Nullable Function<ArrowType, @Nullable String> mapper) {
this.arrowToSqlTypeNameMapping = mapper;
return this;
}