You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by li...@apache.org on 2022/07/07 15:04:32 UTC
[arrow-adbc] branch main updated: Refactor the Derby driver into a JDBC driver (#30)
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 308907b Refactor the Derby driver into a JDBC driver (#30)
308907b is described below
commit 308907b9e29362c4c708622f3fb5545a7d590df8
Author: David Li <li...@gmail.com>
AuthorDate: Thu Jul 7 11:04:27 2022 -0400
Refactor the Derby driver into a JDBC driver (#30)
---
.gitignore | 2 +
.../org/apache/arrow/adbc/core/AdbcConnection.java | 22 +
.../org/apache/arrow/adbc/core/AdbcException.java | 64 ++-
.../{AdbcConnection.java => AdbcStatusCode.java} | 22 +-
.../adbc/drivermanager/AdbcDriverManager.java | 4 +-
java/driver/{derby => jdbc-util}/pom.xml | 28 +-
.../arrow/adbc/driver/jdbc/util/ColumnBinder.java | 57 +++
.../jdbc/util/ColumnBinderArrowTypeVisitor.java | 170 +++++++
.../arrow/adbc/driver/jdbc/util/ColumnBinders.java | 202 ++++++++
.../adbc/driver/jdbc/util/JdbcParameterBinder.java | 158 ++++++
.../driver/jdbc/util/JdbcParameterBinderTest.java | 310 ++++++++++++
.../driver/jdbc/util/MockPreparedStatement.java | 530 +++++++++++++++++++++
java/driver/{derby => jdbc}/pom.xml | 10 +-
.../arrow/adbc/driver/jdbc}/JdbcArrowReader.java | 15 +-
.../arrow/adbc/driver/jdbc/JdbcConnection.java} | 27 +-
.../arrow/adbc/driver/jdbc/JdbcDatabase.java} | 30 +-
.../apache/arrow/adbc/driver/jdbc/JdbcDriver.java} | 10 +-
.../arrow/adbc/driver/jdbc/JdbcDriverUtil.java} | 25 +-
.../arrow/adbc/driver/jdbc/JdbcStatement.java} | 38 +-
.../arrow/adbc/driver/jdbc/JdbcDatabaseTest.java} | 18 +-
java/pom.xml | 21 +-
21 files changed, 1654 insertions(+), 109 deletions(-)
diff --git a/.gitignore b/.gitignore
index 7925456..0bd7eef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -88,3 +88,5 @@ java-dist/
java-native-c/
java-native-cpp/
target/
+
+*.log
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 22bf6fa..f6cb228 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
@@ -18,6 +18,28 @@ package org.apache.arrow.adbc.core;
/** A connection to a {@link AdbcDatabase}. */
public interface AdbcConnection extends AutoCloseable {
+ /**
+ * Commit the pending transaction.
+ *
+ * @throws AdbcException if a database error occurs
+ * @throws IllegalStateException if autocommit is enabled
+ * @throws UnsupportedOperationException if the database does not support transactions
+ */
+ default void commit() throws AdbcException {
+ throw new UnsupportedOperationException("Connection does not support transactions");
+ }
+
/** Create a new statement that can be executed. */
AdbcStatement createStatement() throws AdbcException;
+
+ /**
+ * Rollback the pending transaction.
+ *
+ * @throws AdbcException if a database error occurs
+ * @throws IllegalStateException if autocommit is enabled
+ * @throws UnsupportedOperationException if the database does not support transactions
+ */
+ default void rollback() throws AdbcException {
+ throw new UnsupportedOperationException("Connection does not support transactions");
+ }
}
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 f0845c1..af412af 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
@@ -16,18 +16,68 @@
*/
package org.apache.arrow.adbc.core;
+/**
+ * An error in the database or ADBC driver.
+ *
+ * <p>The exception contains up to five types of information about the error:
+ *
+ * <ul>
+ * <li>An error message
+ * <li>An exception cause
+ * <li>An ADBC status code
+ * <li>A SQLSTATE string
+ * <li>A vendor-specific status code
+ * </ul>
+ *
+ * Driver implementations should also use the following standard exception classes to indicate
+ * invalid API usage:
+ *
+ * <ul>
+ * <li>{@link IllegalArgumentException} for invalid argument values
+ * <li>{@link UnsupportedOperationException} for unimplemented operations
+ * <li>{@link IllegalStateException} for other invalid use of the API (e.g. preconditions not met)
+ * </ul>
+ */
public class AdbcException extends Exception {
- public AdbcException() {}
+ private final AdbcStatusCode status;
+ private final String sqlState;
+ private final int vendorCode;
- public AdbcException(String message) {
- super(message);
+ // TODO: do we also want to support a multi-exception akin to SQLException#setNextException
+ public AdbcException(
+ String message, Throwable cause, AdbcStatusCode status, String sqlState, int vendorCode) {
+ super(message, cause);
+ this.status = status;
+ this.sqlState = sqlState;
+ this.vendorCode = vendorCode;
}
- public AdbcException(String message, Throwable cause) {
- super(message, cause);
+ public AdbcStatusCode getStatus() {
+ return status;
+ }
+
+ public String getSqlState() {
+ return sqlState;
+ }
+
+ public int getVendorCode() {
+ return vendorCode;
}
- public AdbcException(Throwable cause) {
- super(cause);
+ @Override
+ public String toString() {
+ return "AdbcException{"
+ + "message="
+ + getMessage()
+ + ", status="
+ + status
+ + ", sqlState='"
+ + sqlState
+ + '\''
+ + ", vendorCode="
+ + vendorCode
+ + ", cause="
+ + getCause()
+ + '}';
}
}
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/AdbcStatusCode.java
similarity index 67%
copy from java/core/src/main/java/org/apache/arrow/adbc/core/AdbcConnection.java
copy to java/core/src/main/java/org/apache/arrow/adbc/core/AdbcStatusCode.java
index 22bf6fa..c97a384 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/AdbcStatusCode.java
@@ -16,8 +16,22 @@
*/
package org.apache.arrow.adbc.core;
-/** A connection to a {@link AdbcDatabase}. */
-public interface AdbcConnection extends AutoCloseable {
- /** Create a new statement that can be executed. */
- AdbcStatement createStatement() throws AdbcException;
+/**
+ * A status code indicating the general category of error that occurred.
+ *
+ * <p>Also see the ADBC C API definition, which has similar status codes (except here we use
+ * standard Java exceptions to indicate API misuse).
+ */
+public enum AdbcStatusCode {
+ UNKNOWN,
+ NOT_FOUND,
+ ALREADY_EXISTS,
+ INVALID_DATA,
+ INTEGRITY,
+ INTERNAL,
+ IO,
+ CANCELLED,
+ TIMEOUT,
+ UNAUTHENTICATED,
+ UNAUTHORIZED,
}
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 f253e7c..0c8c8f6 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
@@ -23,6 +23,7 @@ import java.util.concurrent.ConcurrentMap;
import org.apache.arrow.adbc.core.AdbcDatabase;
import org.apache.arrow.adbc.core.AdbcDriver;
import org.apache.arrow.adbc.core.AdbcException;
+import org.apache.arrow.adbc.core.AdbcStatusCode;
public final class AdbcDriverManager {
private static final AdbcDriverManager INSTANCE = new AdbcDriverManager();
@@ -45,7 +46,8 @@ public final class AdbcDriverManager {
throws AdbcException {
final AdbcDriver driver = lookupDriver(driverName);
if (driver == null) {
- throw new AdbcException("Driver not found for '" + driverName + "'");
+ throw new AdbcException(
+ "Driver not found for '" + driverName + "'", null, AdbcStatusCode.NOT_FOUND, null, 0);
}
return driver.connect(parameters);
}
diff --git a/java/driver/derby/pom.xml b/java/driver/jdbc-util/pom.xml
similarity index 71%
copy from java/driver/derby/pom.xml
copy to java/driver/jdbc-util/pom.xml
index f0f357a..8aa20f2 100644
--- a/java/driver/derby/pom.xml
+++ b/java/driver/jdbc-util/pom.xml
@@ -18,10 +18,10 @@
<relativePath>../../pom.xml</relativePath>
</parent>
- <artifactId>adbc-driver-derby</artifactId>
+ <artifactId>adbc-driver-jdbc-util</artifactId>
<packaging>jar</packaging>
- <name>Arrow ADBC Core</name>
- <description>An example ADBC driver based on Apache Derby.</description>
+ <name>Arrow ADBC Driver JDBC Util</name>
+ <description>Utilities for working with Arrow and JDBC.</description>
<dependencies>
<!-- Arrow -->
@@ -38,28 +38,6 @@
<artifactId>arrow-vector</artifactId>
</dependency>
- <dependency>
- <groupId>org.apache.arrow.adbc</groupId>
- <artifactId>adbc-core</artifactId>
- </dependency>
- <dependency>
- <groupId>org.apache.arrow.adbc</groupId>
- <artifactId>adbc-driver-manager</artifactId>
- </dependency>
-
- <!-- Derby -->
- <!-- Cannot upgrade beyond this version for Java 8 support -->
- <dependency>
- <groupId>org.apache.derby</groupId>
- <artifactId>derby</artifactId>
- <version>10.14.2.0</version>
- </dependency>
- <dependency>
- <groupId>org.apache.derby</groupId>
- <artifactId>derbytools</artifactId>
- <version>10.14.2.0</version>
- </dependency>
-
<!-- Testing -->
<dependency>
<groupId>org.assertj</groupId>
diff --git a/java/driver/jdbc-util/src/main/java/org/apache/arrow/adbc/driver/jdbc/util/ColumnBinder.java b/java/driver/jdbc-util/src/main/java/org/apache/arrow/adbc/driver/jdbc/util/ColumnBinder.java
new file mode 100644
index 0000000..3f64fc9
--- /dev/null
+++ b/java/driver/jdbc-util/src/main/java/org/apache/arrow/adbc/driver/jdbc/util/ColumnBinder.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.arrow.adbc.driver.jdbc.util;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import org.apache.arrow.vector.FieldVector;
+import org.apache.arrow.vector.types.pojo.ArrowType;
+import org.apache.arrow.vector.types.pojo.Field;
+
+/** A helper to bind values from an Arrow vector to a JDBC PreparedStatement. */
+@FunctionalInterface
+public interface ColumnBinder {
+ /**
+ * Bind the given row to the given parameter.
+ *
+ * @param statement The staement to bind to
+ * @param vector The vector to pull data from
+ * @param parameterIndex The parameter to bind to
+ * @param rowIndex The row to bind
+ * @throws SQLException if an error occurs
+ */
+ void bind(PreparedStatement statement, FieldVector vector, int parameterIndex, int rowIndex)
+ throws SQLException;
+
+ /**
+ * Get an instance of the default binder for a type.
+ *
+ * @throws UnsupportedOperationException if the type is not supported.
+ */
+ static ColumnBinder forType(ArrowType type) {
+ return type.accept(new ColumnBinderArrowTypeVisitor(/*nullable=*/ true));
+ }
+
+ /**
+ * Get an instance of the default binder for a type, accounting for nullability.
+ *
+ * @throws UnsupportedOperationException if the type is not supported.
+ */
+ static ColumnBinder forField(Field field) {
+ return field.getType().accept(new ColumnBinderArrowTypeVisitor(field.isNullable()));
+ }
+}
diff --git a/java/driver/jdbc-util/src/main/java/org/apache/arrow/adbc/driver/jdbc/util/ColumnBinderArrowTypeVisitor.java b/java/driver/jdbc-util/src/main/java/org/apache/arrow/adbc/driver/jdbc/util/ColumnBinderArrowTypeVisitor.java
new file mode 100644
index 0000000..976a87b
--- /dev/null
+++ b/java/driver/jdbc-util/src/main/java/org/apache/arrow/adbc/driver/jdbc/util/ColumnBinderArrowTypeVisitor.java
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.arrow.adbc.driver.jdbc.util;
+
+import org.apache.arrow.vector.types.pojo.ArrowType;
+
+final class ColumnBinderArrowTypeVisitor implements ArrowType.ArrowTypeVisitor<ColumnBinder> {
+ private final boolean nullable;
+
+ public ColumnBinderArrowTypeVisitor(boolean nullable) {
+ this.nullable = nullable;
+ }
+
+ @Override
+ public ColumnBinder visit(ArrowType.Null type) {
+ throw new UnsupportedOperationException("No column binder implemented for type " + type);
+ }
+
+ @Override
+ public ColumnBinder visit(ArrowType.Struct type) {
+ throw new UnsupportedOperationException("No column binder implemented for type " + type);
+ }
+
+ @Override
+ public ColumnBinder visit(ArrowType.List type) {
+ throw new UnsupportedOperationException("No column binder implemented for type " + type);
+ }
+
+ @Override
+ public ColumnBinder visit(ArrowType.LargeList type) {
+ throw new UnsupportedOperationException("No column binder implemented for type " + type);
+ }
+
+ @Override
+ public ColumnBinder visit(ArrowType.FixedSizeList type) {
+ throw new UnsupportedOperationException("No column binder implemented for type " + type);
+ }
+
+ @Override
+ public ColumnBinder visit(ArrowType.Union type) {
+ throw new UnsupportedOperationException("No column binder implemented for type " + type);
+ }
+
+ @Override
+ public ColumnBinder visit(ArrowType.Map type) {
+ throw new UnsupportedOperationException("No column binder implemented for type " + type);
+ }
+
+ @Override
+ public ColumnBinder visit(ArrowType.Int type) {
+ switch (type.getBitWidth()) {
+ case 8:
+ if (type.getIsSigned()) {
+ return nullable
+ ? ColumnBinders.NullableTinyIntBinder.INSTANCE
+ : ColumnBinders.TinyIntBinder.INSTANCE;
+ } else {
+ throw new UnsupportedOperationException(
+ "No column binder implemented for unsigned type " + type);
+ }
+ case 16:
+ if (type.getIsSigned()) {
+ return nullable
+ ? ColumnBinders.NullableSmallIntBinder.INSTANCE
+ : ColumnBinders.SmallIntBinder.INSTANCE;
+ } else {
+ throw new UnsupportedOperationException(
+ "No column binder implemented for unsigned type " + type);
+ }
+ case 32:
+ if (type.getIsSigned()) {
+ return nullable
+ ? ColumnBinders.NullableIntBinder.INSTANCE
+ : ColumnBinders.IntBinder.INSTANCE;
+ } else {
+ throw new UnsupportedOperationException(
+ "No column binder implemented for unsigned type " + type);
+ }
+ case 64:
+ if (type.getIsSigned()) {
+ return nullable
+ ? ColumnBinders.NullableBigIntBinder.INSTANCE
+ : ColumnBinders.BigIntBinder.INSTANCE;
+ } else {
+ throw new UnsupportedOperationException(
+ "No column binder implemented for unsigned type " + type);
+ }
+ }
+ throw new UnsupportedOperationException("No column binder implemented for type " + type);
+ }
+
+ @Override
+ public ColumnBinder visit(ArrowType.FloatingPoint type) {
+ throw new UnsupportedOperationException("No column binder implemented for type " + type);
+ }
+
+ @Override
+ public ColumnBinder visit(ArrowType.Utf8 type) {
+ return ColumnBinders.NullableVarCharBinder.INSTANCE;
+ }
+
+ @Override
+ public ColumnBinder visit(ArrowType.LargeUtf8 type) {
+ return ColumnBinders.NullableLargeVarCharBinder.INSTANCE;
+ }
+
+ @Override
+ public ColumnBinder visit(ArrowType.Binary type) {
+ throw new UnsupportedOperationException("No column binder implemented for type " + type);
+ }
+
+ @Override
+ public ColumnBinder visit(ArrowType.LargeBinary type) {
+ throw new UnsupportedOperationException("No column binder implemented for type " + type);
+ }
+
+ @Override
+ public ColumnBinder visit(ArrowType.FixedSizeBinary type) {
+ throw new UnsupportedOperationException("No column binder implemented for type " + type);
+ }
+
+ @Override
+ public ColumnBinder visit(ArrowType.Bool type) {
+ throw new UnsupportedOperationException("No column binder implemented for type " + type);
+ }
+
+ @Override
+ public ColumnBinder visit(ArrowType.Decimal type) {
+ throw new UnsupportedOperationException("No column binder implemented for type " + type);
+ }
+
+ @Override
+ public ColumnBinder visit(ArrowType.Date type) {
+ throw new UnsupportedOperationException("No column binder implemented for type " + type);
+ }
+
+ @Override
+ public ColumnBinder visit(ArrowType.Time type) {
+ throw new UnsupportedOperationException("No column binder implemented for type " + type);
+ }
+
+ @Override
+ public ColumnBinder visit(ArrowType.Timestamp type) {
+ throw new UnsupportedOperationException("No column binder implemented for type " + type);
+ }
+
+ @Override
+ public ColumnBinder visit(ArrowType.Interval type) {
+ throw new UnsupportedOperationException("No column binder implemented for type " + type);
+ }
+
+ @Override
+ public ColumnBinder visit(ArrowType.Duration type) {
+ throw new UnsupportedOperationException("No column binder implemented for type " + type);
+ }
+}
diff --git a/java/driver/jdbc-util/src/main/java/org/apache/arrow/adbc/driver/jdbc/util/ColumnBinders.java b/java/driver/jdbc-util/src/main/java/org/apache/arrow/adbc/driver/jdbc/util/ColumnBinders.java
new file mode 100644
index 0000000..2ab5117
--- /dev/null
+++ b/java/driver/jdbc-util/src/main/java/org/apache/arrow/adbc/driver/jdbc/util/ColumnBinders.java
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.arrow.adbc.driver.jdbc.util;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Types;
+import org.apache.arrow.vector.BigIntVector;
+import org.apache.arrow.vector.FieldVector;
+import org.apache.arrow.vector.IntVector;
+import org.apache.arrow.vector.LargeVarCharVector;
+import org.apache.arrow.vector.SmallIntVector;
+import org.apache.arrow.vector.TinyIntVector;
+import org.apache.arrow.vector.VarCharVector;
+
+/** Column binder implementations. */
+final class ColumnBinders {
+ // While tedious to define all of these, this avoids unnecessary boxing/unboxing and limits the
+ // call depth compared to trying to be generic with lambdas (hopefully makes inlining easier).
+ // We can consider code templating like arrow-vector for maintenance.
+
+ // ------------------------------------------------------------
+ // Int8
+
+ enum NullableTinyIntBinder implements ColumnBinder {
+ INSTANCE;
+
+ @Override
+ public void bind(
+ PreparedStatement statement, FieldVector vector, int parameterIndex, int rowIndex)
+ throws SQLException {
+ if (vector.isNull(rowIndex)) {
+ statement.setNull(parameterIndex, Types.TINYINT);
+ } else {
+ statement.setByte(parameterIndex, ((TinyIntVector) vector).get(rowIndex));
+ }
+ }
+ }
+
+ enum TinyIntBinder implements ColumnBinder {
+ INSTANCE;
+
+ private static final long BYTE_WIDTH = 1;
+
+ @Override
+ public void bind(
+ PreparedStatement statement, FieldVector vector, int parameterIndex, int rowIndex)
+ throws SQLException {
+ final byte value = vector.getDataBuffer().getByte(rowIndex * BYTE_WIDTH);
+ statement.setByte(parameterIndex, value);
+ }
+ }
+
+ // ------------------------------------------------------------
+ // Int16
+
+ enum NullableSmallIntBinder implements ColumnBinder {
+ INSTANCE;
+
+ @Override
+ public void bind(
+ PreparedStatement statement, FieldVector vector, int parameterIndex, int rowIndex)
+ throws SQLException {
+ if (vector.isNull(rowIndex)) {
+ statement.setNull(parameterIndex, Types.SMALLINT);
+ } else {
+ statement.setShort(parameterIndex, ((SmallIntVector) vector).get(rowIndex));
+ }
+ }
+ }
+
+ enum SmallIntBinder implements ColumnBinder {
+ INSTANCE;
+
+ private static final long BYTE_WIDTH = 2;
+
+ @Override
+ public void bind(
+ PreparedStatement statement, FieldVector vector, int parameterIndex, int rowIndex)
+ throws SQLException {
+ final byte value = vector.getDataBuffer().getByte(rowIndex * BYTE_WIDTH);
+ statement.setByte(parameterIndex, value);
+ }
+ }
+
+ // ------------------------------------------------------------
+ // Int32
+
+ enum NullableIntBinder implements ColumnBinder {
+ INSTANCE;
+
+ @Override
+ public void bind(
+ PreparedStatement statement, FieldVector vector, int parameterIndex, int rowIndex)
+ throws SQLException {
+ if (vector.isNull(rowIndex)) {
+ statement.setNull(parameterIndex, Types.INTEGER);
+ } else {
+ statement.setInt(parameterIndex, ((IntVector) vector).get(rowIndex));
+ }
+ }
+ }
+
+ enum IntBinder implements ColumnBinder {
+ INSTANCE;
+
+ private static final long BYTE_WIDTH = 4;
+
+ @Override
+ public void bind(
+ PreparedStatement statement, FieldVector vector, int parameterIndex, int rowIndex)
+ throws SQLException {
+ final int value = vector.getDataBuffer().getInt(rowIndex * BYTE_WIDTH);
+ statement.setInt(parameterIndex, value);
+ }
+ }
+
+ // ------------------------------------------------------------
+ // Int64
+
+ enum NullableBigIntBinder implements ColumnBinder {
+ INSTANCE;
+
+ @Override
+ public void bind(
+ PreparedStatement statement, FieldVector vector, int parameterIndex, int rowIndex)
+ throws SQLException {
+ if (vector.isNull(rowIndex)) {
+ statement.setNull(parameterIndex, Types.BIGINT);
+ } else {
+ statement.setLong(parameterIndex, ((BigIntVector) vector).get(rowIndex));
+ }
+ }
+ }
+
+ enum BigIntBinder implements ColumnBinder {
+ INSTANCE;
+
+ private static final long BYTE_WIDTH = 4;
+
+ @Override
+ public void bind(
+ PreparedStatement statement, FieldVector vector, int parameterIndex, int rowIndex)
+ throws SQLException {
+ final long value = vector.getDataBuffer().getLong(rowIndex * BYTE_WIDTH);
+ statement.setLong(parameterIndex, value);
+ }
+ }
+
+ // ------------------------------------------------------------
+ // String, LargeString
+
+ // TODO: we may be able to do this generically via ElementAddressableVector
+ // TODO: make this non-singleton so we don't have to allocate when using getDataPointer?
+ // TODO: avoid getObject and just use the byte[] directly (or, can we directly get a String from
+ // an ArrowBuf?)
+ enum NullableVarCharBinder implements ColumnBinder {
+ INSTANCE;
+
+ @Override
+ public void bind(
+ PreparedStatement statement, FieldVector vector, int parameterIndex, int rowIndex)
+ throws SQLException {
+ if (vector.isNull(rowIndex)) {
+ statement.setNull(parameterIndex, Types.VARCHAR);
+ } else {
+ statement.setString(
+ parameterIndex, ((VarCharVector) vector).getObject(rowIndex).toString());
+ }
+ }
+ }
+
+ enum NullableLargeVarCharBinder implements ColumnBinder {
+ INSTANCE;
+
+ @Override
+ public void bind(
+ PreparedStatement statement, FieldVector vector, int parameterIndex, int rowIndex)
+ throws SQLException {
+ if (vector.isNull(rowIndex)) {
+ statement.setNull(parameterIndex, Types.VARCHAR);
+ } else {
+ statement.setString(
+ parameterIndex, ((LargeVarCharVector) vector).getObject(rowIndex).toString());
+ }
+ }
+ }
+}
diff --git a/java/driver/jdbc-util/src/main/java/org/apache/arrow/adbc/driver/jdbc/util/JdbcParameterBinder.java b/java/driver/jdbc-util/src/main/java/org/apache/arrow/adbc/driver/jdbc/util/JdbcParameterBinder.java
new file mode 100644
index 0000000..f67622a
--- /dev/null
+++ b/java/driver/jdbc-util/src/main/java/org/apache/arrow/adbc/driver/jdbc/util/JdbcParameterBinder.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.arrow.adbc.driver.jdbc.util;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Stream;
+import org.apache.arrow.util.Preconditions;
+import org.apache.arrow.vector.FieldVector;
+import org.apache.arrow.vector.VectorSchemaRoot;
+
+/**
+ * Helper to bind values from an Arrow {@link VectorSchemaRoot} to a JDBC {@link PreparedStatement}.
+ */
+public class JdbcParameterBinder {
+ private final PreparedStatement statement;
+ private final VectorSchemaRoot root;
+ private final ColumnBinder[] binders;
+ private final int[] parameterIndices;
+ private final int[] columnIndices;
+ private int nextRowIndex;
+
+ JdbcParameterBinder(
+ final PreparedStatement statement,
+ final VectorSchemaRoot root,
+ final ColumnBinder[] binders,
+ int[] parameterIndices,
+ int[] columnIndices) {
+ Preconditions.checkArgument(
+ parameterIndices.length == columnIndices.length,
+ "Length of parameter indices and column indices must match");
+ this.statement = statement;
+ this.root = root;
+ this.binders = binders;
+ this.parameterIndices = parameterIndices;
+ this.columnIndices = columnIndices;
+ this.nextRowIndex = 0;
+ }
+
+ /**
+ * Create a builder.
+ *
+ * @param statement The statement to bind to.
+ * @param root The root to pull data from.
+ */
+ public static Builder builder(final PreparedStatement statement, final VectorSchemaRoot root) {
+ return new Builder(statement, root);
+ }
+
+ /** Reset the binder (so the root can be updated with new data). */
+ public void reset() {
+ nextRowIndex = 0;
+ }
+
+ /**
+ * Bind the next row to the statement.
+ *
+ * @return true if a row was bound, false if rows were exhausted
+ */
+ public boolean next() throws SQLException {
+ if (nextRowIndex >= root.getRowCount()) {
+ return false;
+ }
+ for (int i = 0; i < parameterIndices.length; i++) {
+ final int parameterIndex = parameterIndices[i];
+ final int columnIndex = columnIndices[i];
+ final FieldVector vector = root.getVector(columnIndex);
+ binders[i].bind(statement, vector, parameterIndex, nextRowIndex);
+ }
+ nextRowIndex++;
+ return true;
+ }
+
+ private static class Binding {
+ int columnIndex;
+ ColumnBinder binder;
+
+ Binding(int columnIndex, ColumnBinder binder) {
+ this.columnIndex = columnIndex;
+ this.binder = binder;
+ }
+ }
+
+ /** A builder for a {@link JdbcParameterBinder}. */
+ public static class Builder {
+ private final PreparedStatement statement;
+ private final VectorSchemaRoot root;
+ // Parameter index -> (Column Index, Binder)
+ private final Map<Integer, Binding> bindings;
+
+ Builder(PreparedStatement statement, VectorSchemaRoot root) {
+ this.statement = statement;
+ this.root = root;
+ this.bindings = new HashMap<>();
+ }
+
+ /** Bind each column to the corresponding parameter in order. */
+ public Builder bindAll() {
+ for (int i = 0; i < root.getFieldVectors().size(); i++) {
+ bind(/*parameterIndex=*/ i + 1, /*columnIndex=*/ i);
+ }
+ return this;
+ }
+
+ /** Bind the given parameter to the given column using the default binder. */
+ public Builder bind(int parameterIndex, int columnIndex) {
+ return bind(
+ parameterIndex,
+ columnIndex,
+ ColumnBinder.forField(root.getVector(columnIndex).getField()));
+ }
+
+ /** Bind the given parameter to the given column using the given binder. */
+ public Builder bind(int parameterIndex, int columnIndex, ColumnBinder binder) {
+ Preconditions.checkArgument(
+ parameterIndex > 0, "parameterIndex %d must be positive", parameterIndex);
+ bindings.put(parameterIndex, new Binding(columnIndex, binder));
+ return this;
+ }
+
+ /** Build the binder. */
+ public JdbcParameterBinder build() {
+ ColumnBinder[] binders = new ColumnBinder[bindings.size()];
+ int[] parameterIndices = new int[bindings.size()];
+ int[] columnIndices = new int[bindings.size()];
+ final Stream<Map.Entry<Integer, Binding>> sortedBindings =
+ bindings.entrySet().stream()
+ .sorted(Comparator.comparingInt(entry -> entry.getValue().columnIndex));
+ int index = 0;
+ for (Map.Entry<Integer, Binding> entry :
+ (Iterable<Map.Entry<Integer, Binding>>) sortedBindings::iterator) {
+ binders[index] = entry.getValue().binder;
+ parameterIndices[index] = entry.getKey();
+ columnIndices[index] = entry.getValue().columnIndex;
+ index++;
+ }
+ return new JdbcParameterBinder(statement, root, binders, parameterIndices, columnIndices);
+ }
+ }
+}
diff --git a/java/driver/jdbc-util/src/test/java/org/apache/arrow/adbc/driver/jdbc/util/JdbcParameterBinderTest.java b/java/driver/jdbc-util/src/test/java/org/apache/arrow/adbc/driver/jdbc/util/JdbcParameterBinderTest.java
new file mode 100644
index 0000000..f08750c
--- /dev/null
+++ b/java/driver/jdbc-util/src/test/java/org/apache/arrow/adbc/driver/jdbc/util/JdbcParameterBinderTest.java
@@ -0,0 +1,310 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.arrow.adbc.driver.jdbc.util;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Arrays;
+import java.util.Collections;
+import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.memory.RootAllocator;
+import org.apache.arrow.vector.BigIntVector;
+import org.apache.arrow.vector.IntVector;
+import org.apache.arrow.vector.SmallIntVector;
+import org.apache.arrow.vector.TinyIntVector;
+import org.apache.arrow.vector.VectorSchemaRoot;
+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.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class JdbcParameterBinderTest {
+ BufferAllocator allocator;
+
+ @BeforeEach
+ void beforeEach() {
+ allocator = new RootAllocator();
+ }
+
+ @AfterEach
+ void afterEach() {
+ allocator.close();
+ }
+
+ @Test
+ void bindOrder() throws SQLException {
+ final Schema schema =
+ new Schema(
+ Arrays.asList(
+ Field.nullable("ints0", new ArrowType.Int(32, true)),
+ Field.nullable("ints1", new ArrowType.Int(32, true)),
+ Field.nullable("ints2", new ArrowType.Int(32, true))));
+ try (final MockPreparedStatement statement = new MockPreparedStatement();
+ final VectorSchemaRoot root = VectorSchemaRoot.create(schema, allocator)) {
+ final JdbcParameterBinder binder =
+ JdbcParameterBinder.builder(statement, root)
+ .bind(/*paramIndex=*/ 1, /*colIndex=*/ 2)
+ .bind(/*paramIndex=*/ 2, /*colIndex=*/ 0)
+ .build();
+ assertThat(binder.next()).isFalse();
+
+ final IntVector ints0 = (IntVector) root.getVector(0);
+ final IntVector ints1 = (IntVector) root.getVector(1);
+ final IntVector ints2 = (IntVector) root.getVector(2);
+ ints0.setSafe(0, 4);
+ ints0.setNull(1);
+ ints1.setNull(0);
+ ints1.setSafe(1, -8);
+ ints2.setNull(0);
+ ints2.setSafe(1, 12);
+ root.setRowCount(2);
+
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isNull();
+ assertThat(statement.getParamType(1)).isEqualTo(Types.INTEGER);
+ assertThat(statement.getParamValue(2)).isInstanceOf(Integer.class).isEqualTo(4);
+ assertThat(statement.getParam(3)).isNull();
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isInstanceOf(Integer.class).isEqualTo(12);
+ assertThat(statement.getParamValue(2)).isNull();
+ assertThat(statement.getParamType(2)).isEqualTo(Types.INTEGER);
+ assertThat(statement.getParam(3)).isNull();
+ assertThat(binder.next()).isFalse();
+
+ binder.reset();
+
+ ints0.setNull(0);
+ ints0.setSafe(1, -2);
+ ints2.setNull(0);
+ ints2.setSafe(1, 6);
+ root.setRowCount(2);
+
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isNull();
+ assertThat(statement.getParamType(1)).isEqualTo(Types.INTEGER);
+ assertThat(statement.getParamValue(2)).isNull();
+ assertThat(statement.getParamType(2)).isEqualTo(Types.INTEGER);
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isInstanceOf(Integer.class).isEqualTo(6);
+ assertThat(statement.getParamValue(2)).isInstanceOf(Integer.class).isEqualTo(-2);
+ assertThat(statement.getParam(3)).isNull();
+ assertThat(binder.next()).isFalse();
+ }
+ }
+
+ @Test
+ void customBinder() throws SQLException {
+ final Schema schema =
+ new Schema(Collections.singletonList(Field.nullable("ints0", new ArrowType.Int(32, true))));
+
+ try (final MockPreparedStatement statement = new MockPreparedStatement();
+ final VectorSchemaRoot root = VectorSchemaRoot.create(schema, allocator)) {
+ final JdbcParameterBinder binder =
+ JdbcParameterBinder.builder(statement, root)
+ .bind(
+ /*paramIndex=*/ 1,
+ /*colIndex=*/ 0,
+ (statement1, vector, parameterIndex, rowIndex) -> {
+ Integer value = ((IntVector) vector).getObject(rowIndex);
+ if (value == null) {
+ statement1.setString(parameterIndex, "null");
+ } else {
+ statement1.setString(parameterIndex, Integer.toString(value));
+ }
+ })
+ .build();
+ assertThat(binder.next()).isFalse();
+
+ final IntVector ints = (IntVector) root.getVector(0);
+ ints.setSafe(0, 4);
+ ints.setNull(1);
+
+ root.setRowCount(2);
+
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isEqualTo("4");
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isEqualTo("null");
+ assertThat(binder.next()).isFalse();
+ }
+ }
+
+ @Test
+ void int8() throws SQLException {
+ final Schema schema =
+ new Schema(Collections.singletonList(Field.nullable("ints", new ArrowType.Int(8, true))));
+ try (final MockPreparedStatement statement = new MockPreparedStatement();
+ final VectorSchemaRoot root = VectorSchemaRoot.create(schema, allocator)) {
+ final JdbcParameterBinder binder =
+ JdbcParameterBinder.builder(statement, root).bindAll().build();
+ assertThat(binder.next()).isFalse();
+
+ final TinyIntVector ints = (TinyIntVector) root.getVector(0);
+ ints.setSafe(0, Byte.MIN_VALUE);
+ ints.setSafe(1, Byte.MAX_VALUE);
+ ints.setNull(2);
+ root.setRowCount(3);
+
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isEqualTo(Byte.MIN_VALUE);
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isEqualTo(Byte.MAX_VALUE);
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isNull();
+ assertThat(statement.getParamType(1)).isEqualTo(Types.TINYINT);
+ assertThat(binder.next()).isFalse();
+
+ binder.reset();
+
+ ints.setNull(0);
+ ints.setSafe(1, 2);
+ root.setRowCount(2);
+
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isNull();
+ assertThat(statement.getParamType(1)).isEqualTo(Types.TINYINT);
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isEqualTo((byte) 2);
+ assertThat(binder.next()).isFalse();
+ }
+ }
+
+ @Test
+ void int16() throws SQLException {
+ final Schema schema =
+ new Schema(Collections.singletonList(Field.nullable("ints", new ArrowType.Int(16, true))));
+ try (final MockPreparedStatement statement = new MockPreparedStatement();
+ final VectorSchemaRoot root = VectorSchemaRoot.create(schema, allocator)) {
+ final JdbcParameterBinder binder =
+ JdbcParameterBinder.builder(statement, root).bindAll().build();
+ assertThat(binder.next()).isFalse();
+
+ final SmallIntVector ints = (SmallIntVector) root.getVector(0);
+ ints.setSafe(0, Short.MIN_VALUE);
+ ints.setSafe(1, Short.MAX_VALUE);
+ ints.setNull(2);
+ root.setRowCount(3);
+
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isEqualTo(Short.MIN_VALUE);
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isEqualTo(Short.MAX_VALUE);
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isNull();
+ assertThat(statement.getParamType(1)).isEqualTo(Types.SMALLINT);
+ assertThat(binder.next()).isFalse();
+
+ binder.reset();
+
+ ints.setNull(0);
+ ints.setSafe(1, 2);
+ root.setRowCount(2);
+
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isNull();
+ assertThat(statement.getParamType(1)).isEqualTo(Types.SMALLINT);
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isEqualTo((short) 2);
+ assertThat(binder.next()).isFalse();
+ }
+ }
+
+ @Test
+ void int32() throws SQLException {
+ final Schema schema =
+ new Schema(Collections.singletonList(Field.nullable("ints", new ArrowType.Int(32, true))));
+ try (final MockPreparedStatement statement = new MockPreparedStatement();
+ final VectorSchemaRoot root = VectorSchemaRoot.create(schema, allocator)) {
+ final JdbcParameterBinder binder =
+ JdbcParameterBinder.builder(statement, root).bindAll().build();
+ assertThat(binder.next()).isFalse();
+
+ final IntVector ints = (IntVector) root.getVector(0);
+ ints.setSafe(0, Integer.MIN_VALUE);
+ ints.setSafe(1, Integer.MAX_VALUE);
+ ints.setNull(2);
+ root.setRowCount(3);
+
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isEqualTo(Integer.MIN_VALUE);
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isEqualTo(Integer.MAX_VALUE);
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isNull();
+ assertThat(statement.getParamType(1)).isEqualTo(Types.INTEGER);
+ assertThat(binder.next()).isFalse();
+
+ binder.reset();
+
+ ints.setNull(0);
+ ints.setSafe(1, 2);
+ root.setRowCount(2);
+
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isNull();
+ assertThat(statement.getParamType(1)).isEqualTo(Types.INTEGER);
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isEqualTo(2);
+ assertThat(binder.next()).isFalse();
+ }
+ }
+
+ @Test
+ void int64() throws SQLException {
+ final Schema schema =
+ new Schema(Collections.singletonList(Field.nullable("ints", new ArrowType.Int(64, true))));
+ try (final MockPreparedStatement statement = new MockPreparedStatement();
+ final VectorSchemaRoot root = VectorSchemaRoot.create(schema, allocator)) {
+ final JdbcParameterBinder binder =
+ JdbcParameterBinder.builder(statement, root).bindAll().build();
+ assertThat(binder.next()).isFalse();
+
+ final BigIntVector ints = (BigIntVector) root.getVector(0);
+ ints.setSafe(0, Long.MIN_VALUE);
+ ints.setSafe(1, Long.MAX_VALUE);
+ ints.setNull(2);
+ root.setRowCount(3);
+
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isEqualTo(Long.MIN_VALUE);
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isEqualTo(Long.MAX_VALUE);
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isNull();
+ assertThat(statement.getParamType(1)).isEqualTo(Types.BIGINT);
+ assertThat(binder.next()).isFalse();
+
+ binder.reset();
+
+ ints.setNull(0);
+ ints.setSafe(1, 2);
+ root.setRowCount(2);
+
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isNull();
+ assertThat(statement.getParamType(1)).isEqualTo(Types.BIGINT);
+ assertThat(binder.next()).isTrue();
+ assertThat(statement.getParamValue(1)).isEqualTo(2L);
+ assertThat(binder.next()).isFalse();
+ }
+ }
+}
diff --git a/java/driver/jdbc-util/src/test/java/org/apache/arrow/adbc/driver/jdbc/util/MockPreparedStatement.java b/java/driver/jdbc-util/src/test/java/org/apache/arrow/adbc/driver/jdbc/util/MockPreparedStatement.java
new file mode 100644
index 0000000..bbaea4b
--- /dev/null
+++ b/java/driver/jdbc-util/src/test/java/org/apache/arrow/adbc/driver/jdbc/util/MockPreparedStatement.java
@@ -0,0 +1,530 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.arrow.adbc.driver.jdbc.util;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.Date;
+import java.sql.NClob;
+import java.sql.ParameterMetaData;
+import java.sql.PreparedStatement;
+import java.sql.Ref;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.RowId;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.SQLXML;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+
+class MockPreparedStatement implements PreparedStatement {
+ /** A PreparedStatement that just stores parameters set on it. */
+ static class ParameterHolder {
+ final Object value;
+ final Integer sqlType;
+
+ ParameterHolder(Object value, Integer sqlType) {
+ this.value = value;
+ this.sqlType = sqlType;
+ }
+ }
+
+ private final Map<Integer, ParameterHolder> parameters;
+
+ MockPreparedStatement() {
+ parameters = new HashMap<>();
+ }
+
+ ParameterHolder getParam(int index) {
+ return parameters.get(index);
+ }
+
+ Object getParamValue(int index) {
+ return parameters.get(index).value;
+ }
+
+ Integer getParamType(int index) {
+ return parameters.get(index).sqlType;
+ }
+
+ @Override
+ public ResultSet executeQuery() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int executeUpdate() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setNull(int parameterIndex, int sqlType) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(null, sqlType));
+ }
+
+ @Override
+ public void setBoolean(int parameterIndex, boolean x) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public void setByte(int parameterIndex, byte x) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public void setShort(int parameterIndex, short x) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public void setInt(int parameterIndex, int x) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public void setLong(int parameterIndex, long x) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public void setFloat(int parameterIndex, float x) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public void setDouble(int parameterIndex, double x) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public void setString(int parameterIndex, String x) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public void setBytes(int parameterIndex, byte[] x) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public void setDate(int parameterIndex, Date x) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public void setTime(int parameterIndex, Time x) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public void clearParameters() throws SQLException {
+ parameters.clear();
+ }
+
+ @Override
+ public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, targetSqlType));
+ }
+
+ @Override
+ public void setObject(int parameterIndex, Object x) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public boolean execute() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addBatch() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setCharacterStream(int parameterIndex, Reader reader, int length)
+ throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(reader, null));
+ }
+
+ @Override
+ public void setRef(int parameterIndex, Ref x) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public void setBlob(int parameterIndex, Blob x) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public void setClob(int parameterIndex, Clob x) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public void setArray(int parameterIndex, Array x) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public ResultSetMetaData getMetaData() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {}
+
+ @Override
+ public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {}
+
+ @Override
+ public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {}
+
+ @Override
+ public void setURL(int parameterIndex, URL x) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public ParameterMetaData getParameterMetaData() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setRowId(int parameterIndex, RowId x) throws SQLException {
+ parameters.put(parameterIndex, new ParameterHolder(x, null));
+ }
+
+ @Override
+ public void setNString(int parameterIndex, String value) throws SQLException {}
+
+ @Override
+ public void setNCharacterStream(int parameterIndex, Reader value, long length)
+ throws SQLException {}
+
+ @Override
+ public void setNClob(int parameterIndex, NClob value) throws SQLException {}
+
+ @Override
+ public void setClob(int parameterIndex, Reader reader, long length) throws SQLException {}
+
+ @Override
+ public void setBlob(int parameterIndex, InputStream inputStream, long length)
+ throws SQLException {}
+
+ @Override
+ public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException {}
+
+ @Override
+ public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException {}
+
+ @Override
+ public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength)
+ throws SQLException {}
+
+ @Override
+ public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException {}
+
+ @Override
+ public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {}
+
+ @Override
+ public void setCharacterStream(int parameterIndex, Reader reader, long length)
+ throws SQLException {}
+
+ @Override
+ public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {}
+
+ @Override
+ public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {}
+
+ @Override
+ public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException {}
+
+ @Override
+ public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException {}
+
+ @Override
+ public void setClob(int parameterIndex, Reader reader) throws SQLException {}
+
+ @Override
+ public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException {}
+
+ @Override
+ public void setNClob(int parameterIndex, Reader reader) throws SQLException {}
+
+ @Override
+ public ResultSet executeQuery(String sql) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int executeUpdate(String sql) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void close() throws SQLException {}
+
+ @Override
+ public int getMaxFieldSize() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setMaxFieldSize(int max) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getMaxRows() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setMaxRows(int max) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setEscapeProcessing(boolean enable) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getQueryTimeout() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setQueryTimeout(int seconds) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void cancel() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SQLWarning getWarnings() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clearWarnings() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setCursorName(String name) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean execute(String sql) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ResultSet getResultSet() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getUpdateCount() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean getMoreResults() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setFetchDirection(int direction) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getFetchDirection() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setFetchSize(int rows) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getFetchSize() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getResultSetConcurrency() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getResultSetType() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void addBatch(String sql) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clearBatch() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int[] executeBatch() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Connection getConnection() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean getMoreResults(int current) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ResultSet getGeneratedKeys() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int executeUpdate(String sql, String[] columnNames) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean execute(String sql, int[] columnIndexes) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean execute(String sql, String[] columnNames) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int getResultSetHoldability() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isClosed() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setPoolable(boolean poolable) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isPoolable() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void closeOnCompletion() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isCloseOnCompletion() throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> T unwrap(Class<T> iface) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isWrapperFor(Class<?> iface) throws SQLException {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/java/driver/derby/pom.xml b/java/driver/jdbc/pom.xml
similarity index 90%
rename from java/driver/derby/pom.xml
rename to java/driver/jdbc/pom.xml
index f0f357a..1437f59 100644
--- a/java/driver/derby/pom.xml
+++ b/java/driver/jdbc/pom.xml
@@ -20,8 +20,8 @@
<artifactId>adbc-driver-derby</artifactId>
<packaging>jar</packaging>
- <name>Arrow ADBC Core</name>
- <description>An example ADBC driver based on Apache Derby.</description>
+ <name>Arrow ADBC Driver JDBC</name>
+ <description>An ADBC driver wrapping the JDBC API.</description>
<dependencies>
<!-- Arrow -->
@@ -42,6 +42,10 @@
<groupId>org.apache.arrow.adbc</groupId>
<artifactId>adbc-core</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.arrow.adbc</groupId>
+ <artifactId>adbc-driver-jdbc-util</artifactId>
+ </dependency>
<dependency>
<groupId>org.apache.arrow.adbc</groupId>
<artifactId>adbc-driver-manager</artifactId>
@@ -53,11 +57,13 @@
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.14.2.0</version>
+ <scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derbytools</artifactId>
<version>10.14.2.0</version>
+ <scope>test</scope>
</dependency>
<!-- Testing -->
diff --git a/java/driver/derby/src/main/java/org/apache/arrow/adbc/driver/derby/JdbcArrowReader.java b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcArrowReader.java
similarity index 85%
rename from java/driver/derby/src/main/java/org/apache/arrow/adbc/driver/derby/JdbcArrowReader.java
rename to java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcArrowReader.java
index bf6b76a..f4b8e73 100644
--- a/java/driver/derby/src/main/java/org/apache/arrow/adbc/driver/derby/JdbcArrowReader.java
+++ b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcArrowReader.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.arrow.adbc.driver.derby;
+package org.apache.arrow.adbc.driver.jdbc;
import java.io.IOException;
import java.sql.ResultSet;
@@ -25,6 +25,7 @@ import org.apache.arrow.adapter.jdbc.JdbcToArrowConfig;
import org.apache.arrow.adapter.jdbc.JdbcToArrowConfigBuilder;
import org.apache.arrow.adapter.jdbc.JdbcToArrowUtils;
import org.apache.arrow.adbc.core.AdbcException;
+import org.apache.arrow.adbc.core.AdbcStatusCode;
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.vector.VectorSchemaRoot;
import org.apache.arrow.vector.VectorUnloader;
@@ -41,8 +42,11 @@ public class JdbcArrowReader extends ArrowReader {
super(allocator);
try {
this.delegate = JdbcToArrow.sqlToArrowVectorIterator(resultSet, allocator);
- } catch (SQLException | IOException e) {
- throw new AdbcException(e);
+ } catch (SQLException e) {
+ throw JdbcDriverUtil.fromSqlException(e);
+ } catch (IOException e) {
+ throw new AdbcException(
+ JdbcDriverUtil.prefixExceptionMessage(e.getMessage()), e, AdbcStatusCode.IO, null, -1);
}
final JdbcToArrowConfig config =
new JdbcToArrowConfigBuilder()
@@ -52,14 +56,15 @@ public class JdbcArrowReader extends ArrowReader {
try {
this.schema = JdbcToArrowUtils.jdbcToArrowSchema(resultSet.getMetaData(), config);
} catch (SQLException e) {
- throw new AdbcException(e);
+ throw JdbcDriverUtil.fromSqlException(e);
}
this.bytesRead = 0;
try {
this.ensureInitialized();
} catch (IOException e) {
- throw new AdbcException(e);
+ throw new AdbcException(
+ JdbcDriverUtil.prefixExceptionMessage(e.getMessage()), e, AdbcStatusCode.IO, null, 0);
}
}
diff --git a/java/driver/derby/src/main/java/org/apache/arrow/adbc/driver/derby/DerbyConnection.java b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcConnection.java
similarity index 68%
rename from java/driver/derby/src/main/java/org/apache/arrow/adbc/driver/derby/DerbyConnection.java
rename to java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcConnection.java
index 4dd3a5a..c2e4091 100644
--- a/java/driver/derby/src/main/java/org/apache/arrow/adbc/driver/derby/DerbyConnection.java
+++ b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcConnection.java
@@ -14,26 +14,45 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.arrow.adbc.driver.derby;
+package org.apache.arrow.adbc.driver.jdbc;
import java.sql.Connection;
+import java.sql.SQLException;
import org.apache.arrow.adbc.core.AdbcConnection;
import org.apache.arrow.adbc.core.AdbcException;
import org.apache.arrow.adbc.core.AdbcStatement;
import org.apache.arrow.memory.BufferAllocator;
-public class DerbyConnection implements AdbcConnection {
+public class JdbcConnection implements AdbcConnection {
private final BufferAllocator allocator;
private final Connection connection;
- DerbyConnection(BufferAllocator allocator, Connection connection) {
+ JdbcConnection(BufferAllocator allocator, Connection connection) {
this.allocator = allocator;
this.connection = connection;
}
+ @Override
+ public void commit() throws AdbcException {
+ try {
+ connection.commit();
+ } catch (SQLException e) {
+ throw JdbcDriverUtil.fromSqlException(e);
+ }
+ }
+
@Override
public AdbcStatement createStatement() throws AdbcException {
- return new DerbyStatement(allocator, connection);
+ return new JdbcStatement(allocator, connection);
+ }
+
+ @Override
+ public void rollback() throws AdbcException {
+ try {
+ connection.rollback();
+ } catch (SQLException e) {
+ throw JdbcDriverUtil.fromSqlException(e);
+ }
}
@Override
diff --git a/java/driver/derby/src/main/java/org/apache/arrow/adbc/driver/derby/DerbyDatabase.java b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDatabase.java
similarity index 67%
rename from java/driver/derby/src/main/java/org/apache/arrow/adbc/driver/derby/DerbyDatabase.java
rename to java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDatabase.java
index b8fd4f5..eded48e 100644
--- a/java/driver/derby/src/main/java/org/apache/arrow/adbc/driver/derby/DerbyDatabase.java
+++ b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDatabase.java
@@ -15,32 +15,33 @@
* limitations under the License.
*/
-package org.apache.arrow.adbc.driver.derby;
+package org.apache.arrow.adbc.driver.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
+import java.util.concurrent.atomic.AtomicInteger;
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;
/** An instance of a database (e.g. a handle to an in-memory database). */
-public final class DerbyDatabase implements AdbcDatabase {
+public final class JdbcDatabase implements AdbcDatabase {
private final BufferAllocator allocator;
private final String target;
private final Connection connection;
+ private final AtomicInteger counter;
- DerbyDatabase(BufferAllocator allocator, final String target) throws AdbcException {
+ JdbcDatabase(BufferAllocator allocator, final String target) throws AdbcException {
+ this.allocator = allocator;
+ this.target = target;
try {
- new org.apache.derby.jdbc.EmbeddedDriver();
-
- this.allocator = allocator;
- this.target = target;
- this.connection = DriverManager.getConnection(target + ";create=true");
+ this.connection = DriverManager.getConnection(target);
} catch (SQLException e) {
- throw new AdbcException(e);
+ throw JdbcDriverUtil.fromSqlException(e);
}
+ this.counter = new AtomicInteger();
}
@Override
@@ -49,11 +50,12 @@ public final class DerbyDatabase implements AdbcDatabase {
try {
connection = DriverManager.getConnection(target);
} catch (SQLException e) {
- throw new AdbcException(e);
+ throw JdbcDriverUtil.fromSqlException(e);
}
- // TODO: better naming of child allocator
- return new DerbyConnection(
- allocator.newChildAllocator("derby-connection", 0, allocator.getLimit()), connection);
+ final int count = counter.getAndIncrement();
+ return new JdbcConnection(
+ allocator.newChildAllocator("adbc-jdbc-connection-" + count, 0, allocator.getLimit()),
+ connection);
}
@Override
@@ -63,6 +65,6 @@ public final class DerbyDatabase implements AdbcDatabase {
@Override
public String toString() {
- return "DerbyDatabase{" + "target='" + target + '\'' + '}';
+ return "JdbcDatabase{" + "target='" + target + '\'' + '}';
}
}
diff --git a/java/driver/derby/src/main/java/org/apache/arrow/adbc/driver/derby/DerbyDriver.java b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDriver.java
similarity index 86%
rename from java/driver/derby/src/main/java/org/apache/arrow/adbc/driver/derby/DerbyDriver.java
rename to java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDriver.java
index 446a109..4a88503 100644
--- a/java/driver/derby/src/main/java/org/apache/arrow/adbc/driver/derby/DerbyDriver.java
+++ b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDriver.java
@@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.arrow.adbc.driver.derby;
+package org.apache.arrow.adbc.driver.jdbc;
import java.util.Map;
import org.apache.arrow.adbc.core.AdbcDatabase;
@@ -24,18 +24,18 @@ import org.apache.arrow.adbc.drivermanager.AdbcDriverManager;
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.memory.RootAllocator;
-public enum DerbyDriver implements AdbcDriver {
+public enum JdbcDriver implements AdbcDriver {
INSTANCE;
private final BufferAllocator allocator;
- DerbyDriver() {
+ JdbcDriver() {
allocator = new RootAllocator();
- AdbcDriverManager.getInstance().registerDriver("org.apache.arrow.adbc.driver.derby", this);
+ AdbcDriverManager.getInstance().registerDriver("org.apache.arrow.adbc.driver.jdbc", this);
}
@Override
public AdbcDatabase connect(Map<String, String> parameters) throws AdbcException {
- return new DerbyDatabase(allocator, "jdbc:derby:" + parameters.get("path"));
+ return new JdbcDatabase(allocator, "jdbc:derby:" + parameters.get("path"));
}
}
diff --git a/java/core/src/main/java/org/apache/arrow/adbc/core/AdbcException.java b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDriverUtil.java
similarity index 56%
copy from java/core/src/main/java/org/apache/arrow/adbc/core/AdbcException.java
copy to java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDriverUtil.java
index f0845c1..80fcec6 100644
--- a/java/core/src/main/java/org/apache/arrow/adbc/core/AdbcException.java
+++ b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDriverUtil.java
@@ -14,20 +14,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.arrow.adbc.core;
+package org.apache.arrow.adbc.driver.jdbc;
-public class AdbcException extends Exception {
- public AdbcException() {}
+import java.sql.SQLException;
+import org.apache.arrow.adbc.core.AdbcException;
+import org.apache.arrow.adbc.core.AdbcStatusCode;
- public AdbcException(String message) {
- super(message);
+final class JdbcDriverUtil {
+ private JdbcDriverUtil() {
+ throw new AssertionError("Do not instantiate this class");
}
- public AdbcException(String message, Throwable cause) {
- super(message, cause);
+ static String prefixExceptionMessage(final String s) {
+ return "[JDBC] " + s;
}
- public AdbcException(Throwable cause) {
- super(cause);
+ static AdbcException fromSqlException(final SQLException e) {
+ return new AdbcException(
+ prefixExceptionMessage(e.getMessage()),
+ e.getCause(),
+ AdbcStatusCode.UNKNOWN,
+ e.getSQLState(),
+ e.getErrorCode());
}
}
diff --git a/java/driver/derby/src/main/java/org/apache/arrow/adbc/driver/derby/DerbyStatement.java b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcStatement.java
similarity index 83%
rename from java/driver/derby/src/main/java/org/apache/arrow/adbc/driver/derby/DerbyStatement.java
rename to java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcStatement.java
index 6abd69e..d21fc80 100644
--- a/java/driver/derby/src/main/java/org/apache/arrow/adbc/driver/derby/DerbyStatement.java
+++ b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcStatement.java
@@ -15,25 +15,23 @@
* limitations under the License.
*/
-package org.apache.arrow.adbc.driver.derby;
+package org.apache.arrow.adbc.driver.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
-import java.sql.Types;
import java.util.Objects;
import org.apache.arrow.adbc.core.AdbcException;
import org.apache.arrow.adbc.core.AdbcStatement;
+import org.apache.arrow.adbc.driver.jdbc.util.JdbcParameterBinder;
import org.apache.arrow.memory.BufferAllocator;
-import org.apache.arrow.vector.FieldVector;
-import org.apache.arrow.vector.IntVector;
import org.apache.arrow.vector.VectorSchemaRoot;
import org.apache.arrow.vector.ipc.ArrowReader;
import org.apache.arrow.vector.types.pojo.Field;
-public class DerbyStatement implements AdbcStatement {
+public class JdbcStatement implements AdbcStatement {
private final BufferAllocator allocator;
private final Connection connection;
@@ -44,7 +42,7 @@ public class DerbyStatement implements AdbcStatement {
private String bulkTargetTable;
private VectorSchemaRoot bindRoot;
- DerbyStatement(BufferAllocator allocator, Connection connection) {
+ JdbcStatement(BufferAllocator allocator, Connection connection) {
this.allocator = allocator;
this.connection = connection;
this.sqlQuery = null;
@@ -132,10 +130,11 @@ public class DerbyStatement implements AdbcStatement {
try (final Statement statement = connection.createStatement()) {
statement.execute(create.toString());
} catch (SQLException e) {
- throw new AdbcException(e);
+ throw JdbcDriverUtil.fromSqlException(e);
}
// XXX: potential injection
+ // TODO: consider (optionally?) depending on jOOQ to generate SQL and support different dialects
final StringBuilder insert = new StringBuilder("INSERT INTO ");
insert.append(bulkTargetTable);
insert.append(" VALUES (");
@@ -148,26 +147,15 @@ public class DerbyStatement implements AdbcStatement {
insert.append(")");
try (final PreparedStatement statement = connection.prepareStatement(insert.toString())) {
+ final JdbcParameterBinder binder =
+ JdbcParameterBinder.builder(statement, bindRoot).bindAll().build();
statement.clearBatch();
- for (int row = 0; row < bindRoot.getRowCount(); row++) {
- for (int col = 0; col < bindRoot.getFieldVectors().size(); col++) {
- final int parameterIndex = col + 1;
- final FieldVector vector = bindRoot.getVector(col);
- if (vector instanceof IntVector) {
- if (vector.isNull(row)) {
- statement.setNull(parameterIndex, Types.BIGINT);
- } else {
- statement.setLong(parameterIndex, ((IntVector) vector).get(row));
- }
- } else {
- throw new UnsupportedOperationException("Unsupported Arrow type: " + vector.getField());
- }
- }
+ while (binder.next()) {
statement.addBatch();
}
statement.executeBatch();
} catch (SQLException e) {
- throw new AdbcException(e);
+ throw JdbcDriverUtil.fromSqlException(e);
}
}
@@ -180,7 +168,7 @@ public class DerbyStatement implements AdbcStatement {
connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
resultSet = statement.executeQuery(sqlQuery);
} catch (SQLException e) {
- throw new AdbcException(e);
+ throw JdbcDriverUtil.fromSqlException(e);
}
}
@@ -195,7 +183,9 @@ public class DerbyStatement implements AdbcStatement {
}
@Override
- public void prepare() {}
+ public void prepare() {
+ throw new UnsupportedOperationException("prepare");
+ }
@Override
public void close() throws Exception {
diff --git a/java/driver/derby/src/test/java/org/apache/arrow/adbc/driver/derby/DerbyDatabaseTest.java b/java/driver/jdbc/src/test/java/org/apache/arrow/adbc/driver/jdbc/JdbcDatabaseTest.java
similarity index 88%
rename from java/driver/derby/src/test/java/org/apache/arrow/adbc/driver/derby/DerbyDatabaseTest.java
rename to java/driver/jdbc/src/test/java/org/apache/arrow/adbc/driver/jdbc/JdbcDatabaseTest.java
index 3bf6459..f80649f 100644
--- a/java/driver/derby/src/test/java/org/apache/arrow/adbc/driver/derby/DerbyDatabaseTest.java
+++ b/java/driver/jdbc/src/test/java/org/apache/arrow/adbc/driver/jdbc/JdbcDatabaseTest.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package org.apache.arrow.adbc.driver.derby;
+package org.apache.arrow.adbc.driver.jdbc;
import static org.assertj.core.api.Assertions.assertThat;
@@ -35,15 +35,21 @@ 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.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
-class DerbyDatabaseTest {
+class JdbcDatabaseTest {
+ @BeforeAll
+ static void beforeAll() {
+ new org.apache.derby.jdbc.EmbeddedDriver();
+ }
+
@Test
void simpleQuery(@TempDir Path tempDir) throws Exception {
final Map<String, String> parameters = new HashMap<>();
- parameters.put("path", tempDir.toString() + "/db");
- try (final AdbcDatabase database = DerbyDriver.INSTANCE.connect(parameters);
+ parameters.put("path", tempDir.toString() + "/db;create=true");
+ try (final AdbcDatabase database = JdbcDriver.INSTANCE.connect(parameters);
final AdbcConnection connection = database.connect();
final AdbcStatement statement = connection.createStatement()) {
statement.setSqlQuery("SELECT * FROM SYS.SYSTABLES");
@@ -57,13 +63,13 @@ class DerbyDatabaseTest {
@Test
void bulkInsert(@TempDir Path tempDir) throws Exception {
final Map<String, String> parameters = new HashMap<>();
- parameters.put("path", tempDir.toString() + "/db");
+ parameters.put("path", tempDir.toString() + "/db;create=true");
final Schema schema =
new Schema(
Collections.singletonList(
Field.nullable("ints", new ArrowType.Int(32, /*signed=*/ true))));
try (final BufferAllocator allocator = new RootAllocator();
- final AdbcDatabase database = DerbyDriver.INSTANCE.connect(parameters);
+ final AdbcDatabase database = JdbcDriver.INSTANCE.connect(parameters);
final AdbcConnection connection = database.connect()) {
try (final AdbcStatement statement = connection.createStatement();
final VectorSchemaRoot ingest = VectorSchemaRoot.create(schema, allocator)) {
diff --git a/java/pom.xml b/java/pom.xml
index 5e11b85..8645c11 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -29,6 +29,7 @@
<properties>
<dep.arrow.version>8.0.0</dep.arrow.version>
+ <adbc.version>9.0.0-SNAPSHOT</adbc.version>
</properties>
<scm>
@@ -67,7 +68,8 @@
<modules>
<module>core</module>
- <module>driver/derby</module>
+ <module>driver/jdbc</module>
+ <module>driver/jdbc-util</module>
<module>driver-manager</module>
</modules>
@@ -93,12 +95,17 @@
<dependency>
<groupId>org.apache.arrow.adbc</groupId>
<artifactId>adbc-core</artifactId>
- <version>9.0.0-SNAPSHOT</version>
+ <version>${adbc.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.arrow.adbc</groupId>
+ <artifactId>adbc-driver-jdbc-util</artifactId>
+ <version>${adbc.version}</version>
</dependency>
<dependency>
<groupId>org.apache.arrow.adbc</groupId>
<artifactId>adbc-driver-manager</artifactId>
- <version>9.0.0-SNAPSHOT</version>
+ <version>${adbc.version}</version>
</dependency>
<!-- Testing -->
@@ -156,6 +163,14 @@
</goals>
</execution>
</executions>
+ <configuration>
+ <excludeSubProjects>false</excludeSubProjects>
+ <excludes>
+ <exclude>**/*.iml</exclude>
+ <exclude>**/*.log</exclude>
+ <exclude>**/target/**</exclude>
+ </excludes>
+ </configuration>
</plugin>
</plugins>
</build>