diff --git a/java/driver/jdbc-validation-mssqlserver/pom.xml b/java/driver/jdbc-validation-mssqlserver/pom.xml new file mode 100644 index 0000000000..5a585df434 --- /dev/null +++ b/java/driver/jdbc-validation-mssqlserver/pom.xml @@ -0,0 +1,65 @@ + + + + 4.0.0 + + arrow-adbc-java-root + org.apache.arrow.adbc + 0.5.0-SNAPSHOT + ../../pom.xml + + + adbc-driver-jdbc-validation-mssqlserver + jar + Arrow ADBC Driver JDBC Validation with Microsoft SQL Server + Tests validating the JDBC driver against Microsoft SQL Server. + + + true + + + + + org.apache.arrow.adbc + adbc-core + test + + + org.apache.arrow.adbc + adbc-driver-jdbc + test + + + + com.microsoft.sqlserver + mssql-jdbc + 12.2.0.jre11 + + + + + org.assertj + assertj-core + test + + + org.junit.jupiter + junit-jupiter + test + + + org.apache.arrow.adbc + adbc-driver-validation + test + + + diff --git a/java/driver/jdbc-validation-mssqlserver/src/test/java/org/apache/arrow/adbc/driver/jdbc/mssqlserver/MsSqlServerQuirks.java b/java/driver/jdbc-validation-mssqlserver/src/test/java/org/apache/arrow/adbc/driver/jdbc/mssqlserver/MsSqlServerQuirks.java new file mode 100644 index 0000000000..43641b469b --- /dev/null +++ b/java/driver/jdbc-validation-mssqlserver/src/test/java/org/apache/arrow/adbc/driver/jdbc/mssqlserver/MsSqlServerQuirks.java @@ -0,0 +1,92 @@ +/* + * 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.mssqlserver; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.HashMap; +import java.util.Map; +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.driver.jdbc.JdbcDriver; +import org.apache.arrow.adbc.driver.jdbc.adapter.JdbcToArrowTypeConverters; +import org.apache.arrow.adbc.driver.testsuite.SqlValidationQuirks; +import org.apache.arrow.memory.BufferAllocator; +import org.apache.arrow.vector.types.TimeUnit; +import org.junit.jupiter.api.Assumptions; + +public class MsSqlServerQuirks extends SqlValidationQuirks { + static final String URL_ENV_VAR = "ADBC_JDBC_MSSQL_URL"; + static final String USER_ENV_VAR = "ADBC_JDBC_MSSQL_USER"; + static final String PASSWORD_ENV_VAR = "ADBC_JDBC_MSSQL_PASSWORD"; + + static String makeJdbcUrl() { + final String url = System.getenv(URL_ENV_VAR); + final String user = System.getenv(USER_ENV_VAR); + final String password = System.getenv(PASSWORD_ENV_VAR); + Assumptions.assumeFalse(url == null, "Microsoft SQL Server not found, set " + URL_ENV_VAR); + Assumptions.assumeFalse(url.isEmpty(), "Microsoft SQL Server not found, set " + URL_ENV_VAR); + return String.format( + "jdbc:sqlserver://%s;user=%s;password=%s;trustServerCertificate=true", url, user, password); + } + + @Override + public AdbcDatabase initDatabase(BufferAllocator allocator) throws AdbcException { + String url = makeJdbcUrl(); + + final Map parameters = new HashMap<>(); + parameters.put(AdbcDriver.PARAM_URL, url); + parameters.put( + JdbcDriver.PARAM_JDBC_TO_ARROW_TYPE, JdbcToArrowTypeConverters.MICROSOFT_SQL_SERVER); + return new JdbcDriver(allocator).open(parameters); + } + + @Override + public void cleanupTable(String name) throws Exception { + try (final Connection connection1 = DriverManager.getConnection(makeJdbcUrl())) { + try (Statement statement = connection1.createStatement()) { + statement.execute("DROP TABLE " + name); + } catch (SQLException ignored) { + } + } + } + + @Override + public String defaultCatalog() { + // XXX: this should really come from configuration + return "msdb"; + } + + @Override + public String caseFoldTableName(String name) { + return name.toLowerCase(); + } + + @Override + public String caseFoldColumnName(String name) { + return name.toLowerCase(); + } + + @Override + public TimeUnit defaultTimestampUnit() { + return TimeUnit.NANOSECOND; + } +} diff --git a/java/driver/jdbc-validation-mssqlserver/src/test/java/org/apache/arrow/adbc/driver/jdbc/mssqlserver/MsSqlServerSqlTypeTest.java b/java/driver/jdbc-validation-mssqlserver/src/test/java/org/apache/arrow/adbc/driver/jdbc/mssqlserver/MsSqlServerSqlTypeTest.java new file mode 100644 index 0000000000..486aeef972 --- /dev/null +++ b/java/driver/jdbc-validation-mssqlserver/src/test/java/org/apache/arrow/adbc/driver/jdbc/mssqlserver/MsSqlServerSqlTypeTest.java @@ -0,0 +1,49 @@ +/* + * 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.mssqlserver; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.apache.arrow.adbc.driver.testsuite.AbstractSqlTypeTest; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class MsSqlServerSqlTypeTest extends AbstractSqlTypeTest { + @BeforeAll + public static void beforeAll() { + quirks = new MsSqlServerQuirks(); + } + + @Test + @Override + protected void timestampWithoutTimeZoneValue() { + // TODO(https://github.com/apache/arrow/issues/35916): needs upstream fix + // XXX: Java 8 compiler complains without lambda https://stackoverflow.com/questions/33621060 + //noinspection Convert2MethodRef + assertThrows(RuntimeException.class, () -> super.timestampWithTimeZoneValue()); + } + + @Test + @Override + protected void timestampWithTimeZoneValue() { + // TODO(https://github.com/apache/arrow/issues/35916): needs upstream fix + // XXX: Java 8 compiler complains without lambda https://stackoverflow.com/questions/33621060 + //noinspection Convert2MethodRef + assertThrows(RuntimeException.class, () -> super.timestampWithTimeZoneValue()); + } +} diff --git a/java/driver/jdbc-validation-mssqlserver/src/test/resources/MsSqlServerSqlTypeTest.sql b/java/driver/jdbc-validation-mssqlserver/src/test/resources/MsSqlServerSqlTypeTest.sql new file mode 100644 index 0000000000..82a00e6dc9 --- /dev/null +++ b/java/driver/jdbc-validation-mssqlserver/src/test/resources/MsSqlServerSqlTypeTest.sql @@ -0,0 +1,44 @@ +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You under the Apache License, Version 2.0 +-- (the "License"); you may not use this file except in compliance with +-- the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +DROP TABLE IF EXISTS adbc_alltypes; + +CREATE TABLE adbc_alltypes ( + bigint_t BIGINT, + date_t DATE, + int_t INT, + text_t TEXT, + time_without_time_zone_t TIME, + timestamp_without_time_zone_t DATETIME2, + timestamp_with_time_zone_t DATETIMEOFFSET +); + +INSERT INTO adbc_alltypes VALUES ( + 42, + '2000-01-01', + 42, + 'foo', + '04:05:06.789012', + '2000-01-02T03:04:05.123456', + '2000-01-02T03:04:05.123456+06:00' +), ( + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +); 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 new file mode 100644 index 0000000000..0eab44c3ac --- /dev/null +++ b/java/driver/jdbc-validation-postgresql/src/test/java/org/apache/arrow/adbc/driver/jdbc/postgresql/PostgreSqlTypeTest.java @@ -0,0 +1,102 @@ +/* + * 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.postgresql; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.apache.arrow.adbc.driver.testsuite.AbstractSqlTypeTest; +import org.apache.arrow.vector.types.TimeUnit; +import org.apache.arrow.vector.types.pojo.ArrowType; +import org.apache.arrow.vector.types.pojo.Schema; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class PostgreSqlTypeTest extends AbstractSqlTypeTest { + @BeforeAll + public static void beforeAll() { + quirks = new PostgresqlQuirks(); + } + + @Test + @Override + protected void timestampWithoutTimeZoneValue() { + // TODO(https://github.com/apache/arrow/issues/35916): needs upstream fix + // XXX: Java 8 compiler complains without lambda https://stackoverflow.com/questions/33621060 + //noinspection Convert2MethodRef + assertThrows(AssertionError.class, () -> super.timestampWithTimeZoneValue()); + } + + @Test + @Override + protected void timestampWithTimeZoneValue() { + // TODO(https://github.com/apache/arrow/issues/35916): needs upstream fix + // XXX: Java 8 compiler complains without lambda https://stackoverflow.com/questions/33621060 + //noinspection Convert2MethodRef + assertThrows(AssertionError.class, () -> super.timestampWithTimeZoneValue()); + } + + @Test + protected void timestamp6WithoutTimeZoneType() throws Exception { + final Schema schema = connection.getTableSchema(null, null, "adbc_alltypes"); + assertThat(schema.findField("timestamp_without_time_zone_p6_t").getType()) + .isEqualTo(new ArrowType.Timestamp(TimeUnit.MICROSECOND, null)); + } + + @Test + protected void timestamp5WithoutTimeZoneType() throws Exception { + final Schema schema = connection.getTableSchema(null, null, "adbc_alltypes"); + assertThat(schema.findField("timestamp_without_time_zone_p5_t").getType()) + .isEqualTo(new ArrowType.Timestamp(TimeUnit.MICROSECOND, null)); + } + + @Test + protected void timestamp4WithoutTimeZoneType() throws Exception { + final Schema schema = connection.getTableSchema(null, null, "adbc_alltypes"); + assertThat(schema.findField("timestamp_without_time_zone_p4_t").getType()) + .isEqualTo(new ArrowType.Timestamp(TimeUnit.MICROSECOND, null)); + } + + @Test + 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.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.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.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.SECOND, null)); + } +} diff --git a/java/driver/jdbc-validation-postgresql/src/test/java/org/apache/arrow/adbc/driver/jdbc/postgresql/PostgresqlQuirks.java b/java/driver/jdbc-validation-postgresql/src/test/java/org/apache/arrow/adbc/driver/jdbc/postgresql/PostgresqlQuirks.java index 0cc6b8779b..f98ec3d28c 100644 --- a/java/driver/jdbc-validation-postgresql/src/test/java/org/apache/arrow/adbc/driver/jdbc/postgresql/PostgresqlQuirks.java +++ b/java/driver/jdbc-validation-postgresql/src/test/java/org/apache/arrow/adbc/driver/jdbc/postgresql/PostgresqlQuirks.java @@ -27,9 +27,11 @@ import org.apache.arrow.adbc.core.AdbcDriver; import org.apache.arrow.adbc.core.AdbcException; import org.apache.arrow.adbc.driver.jdbc.JdbcDriver; +import org.apache.arrow.adbc.driver.jdbc.adapter.JdbcToArrowTypeConverters; import org.apache.arrow.adbc.driver.testsuite.SqlValidationQuirks; import org.apache.arrow.adbc.sql.SqlQuirks; import org.apache.arrow.memory.BufferAllocator; +import org.apache.arrow.vector.types.TimeUnit; import org.apache.arrow.vector.types.pojo.ArrowType; import org.junit.jupiter.api.Assumptions; @@ -66,6 +68,7 @@ public AdbcDatabase initDatabase(BufferAllocator allocator) throws AdbcException return SqlQuirks.DEFAULT_ARROW_TYPE_TO_SQL_TYPE_NAME_MAPPING.apply(arrowType); })) .build()); + parameters.put(JdbcDriver.PARAM_JDBC_TO_ARROW_TYPE, JdbcToArrowTypeConverters.POSTGRESQL); return new JdbcDriver(allocator).open(parameters); } @@ -94,4 +97,9 @@ public String caseFoldTableName(String name) { public String caseFoldColumnName(String name) { return name.toLowerCase(); } + + @Override + public TimeUnit defaultTimestampUnit() { + return TimeUnit.MICROSECOND; + } } diff --git a/java/driver/jdbc-validation-postgresql/src/test/resources/PostgreSqlTypeTest.sql b/java/driver/jdbc-validation-postgresql/src/test/resources/PostgreSqlTypeTest.sql new file mode 100644 index 0000000000..5ce12a32f3 --- /dev/null +++ b/java/driver/jdbc-validation-postgresql/src/test/resources/PostgreSqlTypeTest.sql @@ -0,0 +1,68 @@ +-- 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. + +DROP TABLE IF EXISTS adbc_alltypes; + +CREATE TABLE adbc_alltypes ( + bigint_t BIGINT, + date_t DATE, + int_t INT, + text_t TEXT, + time_without_time_zone_t TIME WITHOUT TIME ZONE, + time_with_time_zone_t TIME WITH TIME ZONE, + timestamp_without_time_zone_t TIMESTAMP WITHOUT TIME ZONE, + timestamp_without_time_zone_p6_t TIMESTAMP (6) WITHOUT TIME ZONE, + timestamp_without_time_zone_p5_t TIMESTAMP (5) WITHOUT TIME ZONE, + timestamp_without_time_zone_p4_t TIMESTAMP (4) WITHOUT TIME ZONE, + timestamp_without_time_zone_p3_t TIMESTAMP (3) WITHOUT TIME ZONE, + timestamp_without_time_zone_p2_t TIMESTAMP (2) WITHOUT TIME ZONE, + timestamp_without_time_zone_p1_t TIMESTAMP (1) WITHOUT TIME ZONE, + timestamp_without_time_zone_p0_t TIMESTAMP (0) WITHOUT TIME ZONE, + timestamp_with_time_zone_t TIMESTAMP WITH TIME ZONE +); + +INSERT INTO adbc_alltypes VALUES ( + 42, + '2000-01-01', + 42, + 'foo', + '04:05:06.789012', + '04:05:06.789012-08:00', + TIMESTAMP '2000-01-02 03:04:05.123456', + TIMESTAMP (6) '2000-01-02 03:04:05.123456', + TIMESTAMP (5) '2000-01-02 03:04:05.12345', + TIMESTAMP (4) '2000-01-02 03:04:05.1234', + TIMESTAMP (3) '2000-01-02 03:04:05.123', + TIMESTAMP (2) '2000-01-02 03:04:05.12', + TIMESTAMP (1) '2000-01-02 03:04:05.1', + TIMESTAMP (0) '2000-01-02 03:04:05', + TIMESTAMP WITH TIME ZONE '2000-01-02 03:04:05.123456+06' +), ( + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +); 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 658271678a..9274ea9167 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 @@ -22,7 +22,6 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -import org.apache.arrow.adapter.jdbc.JdbcFieldInfo; import org.apache.arrow.adapter.jdbc.JdbcToArrowUtils; import org.apache.arrow.adbc.core.AdbcConnection; import org.apache.arrow.adbc.core.AdbcException; @@ -31,6 +30,8 @@ import org.apache.arrow.adbc.core.BulkIngestMode; import org.apache.arrow.adbc.core.IsolationLevel; import org.apache.arrow.adbc.core.StandardSchemas; +import org.apache.arrow.adbc.driver.jdbc.adapter.JdbcFieldInfoExtra; +import org.apache.arrow.adbc.driver.jdbc.adapter.JdbcToArrowTypeConverter; import org.apache.arrow.adbc.sql.SqlQuirks; import org.apache.arrow.memory.BufferAllocator; import org.apache.arrow.util.AutoCloseables; @@ -45,6 +46,7 @@ public class JdbcConnection implements AdbcConnection { private final BufferAllocator allocator; private final Connection connection; private final SqlQuirks quirks; + private final JdbcToArrowTypeConverter typeConverter; /** * Create a new connection. @@ -53,10 +55,15 @@ public class JdbcConnection implements AdbcConnection { * @param connection The JDBC connection. * @param quirks Backend-specific quirks to account for. */ - JdbcConnection(BufferAllocator allocator, Connection connection, SqlQuirks quirks) { + JdbcConnection( + BufferAllocator allocator, + Connection connection, + SqlQuirks quirks, + JdbcToArrowTypeConverter typeConverter) { this.allocator = allocator; this.connection = connection; this.quirks = quirks; + this.typeConverter = typeConverter; } @Override @@ -123,6 +130,8 @@ public ArrowReader getObjects( public Schema getTableSchema(String catalog, String dbSchema, String tableName) throws AdbcException { // Check for existence + // XXX: this is TOC/TOU error, but without an explicit check we just get no fields (possibly we + // should just assume it's impossible to have a 0-column table and error there?) try (final ResultSet rs = connection.getMetaData().getTables(catalog, dbSchema, tableName, /*tableTypes*/ null)) { if (!rs.next()) { @@ -146,19 +155,32 @@ public Schema getTableSchema(String catalog, String dbSchema, String tableName) .getColumns(catalog, dbSchema, tableName, /*columnNamePattern*/ null)) { while (rs.next()) { final String fieldName = rs.getString("COLUMN_NAME"); - final boolean nullable = rs.getInt("NULLABLE") != DatabaseMetaData.columnNoNulls; - final int jdbcType = rs.getInt("DATA_TYPE"); - final int precision = rs.getInt("COLUMN_SIZE"); - final int scale = rs.getInt("DECIMAL_DIGITS"); - final ArrowType arrowType = - JdbcToArrowUtils.getArrowTypeFromJdbcType( - new JdbcFieldInfo(jdbcType, precision, scale), /*calendar*/ null); + final JdbcFieldInfoExtra fieldInfoExtra = new JdbcFieldInfoExtra(rs); + + final ArrowType arrowType; + if (typeConverter != null) { + arrowType = typeConverter.apply(fieldInfoExtra); + } else { + arrowType = + JdbcToArrowUtils.getArrowTypeFromJdbcType( + fieldInfoExtra.getFieldInfo(), /*calendar*/ null); + } + if (arrowType == null) { + throw AdbcException.notImplemented( + JdbcDriverUtil.prefixExceptionMessage( + String.format( + "Column '%s' has unsupported type: %s", fieldName, fieldInfoExtra))); + } + final Field field = new Field( fieldName, new FieldType( - nullable, arrowType, /*dictionary*/ null, /*metadata*/ null), /*children*/ - null); + fieldInfoExtra.isNullable() != DatabaseMetaData.columnNoNulls, + arrowType, /*dictionary*/ + null, /*metadata*/ + null), + /*children*/ null); 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 53adc362b7..203fec89f0 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 @@ -25,6 +25,7 @@ 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.adbc.driver.jdbc.adapter.JdbcToArrowTypeConverter; import org.apache.arrow.adbc.sql.SqlQuirks; import org.apache.arrow.memory.BufferAllocator; @@ -37,19 +38,22 @@ public final class JdbcDataSourceDatabase implements AdbcDatabase { private final SqlQuirks quirks; private final Connection connection; private final AtomicInteger counter; + private final JdbcToArrowTypeConverter typeConverter; JdbcDataSourceDatabase( BufferAllocator allocator, DataSource dataSource, String username, String password, - SqlQuirks quirks) + SqlQuirks quirks, + JdbcToArrowTypeConverter typeConverter) throws AdbcException { this.allocator = Objects.requireNonNull(allocator); this.dataSource = Objects.requireNonNull(dataSource); this.username = username; this.password = password; this.quirks = Objects.requireNonNull(quirks); + this.typeConverter = typeConverter; try { this.connection = dataSource.getConnection(); } catch (SQLException e) { @@ -75,7 +79,8 @@ public AdbcConnection connect() throws AdbcException { allocator.newChildAllocator( "adbc-jdbc-datasource-connection-" + count, 0, allocator.getLimit()), connection, - quirks); + quirks, + typeConverter); } @Override diff --git a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDatabase.java b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDatabase.java index 38494bc9f1..bcfffc70e8 100644 --- a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDatabase.java +++ b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/JdbcDatabase.java @@ -66,7 +66,8 @@ public AdbcConnection connect() throws AdbcException { return new JdbcConnection( allocator.newChildAllocator("adbc-jdbc-connection-" + count, 0, allocator.getLimit()), connection, - quirks); + quirks, /*typeConverter*/ + null); } @Override 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 71e0d12c71..92cf373095 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 @@ -23,6 +23,7 @@ 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.driver.jdbc.adapter.JdbcToArrowTypeConverter; import org.apache.arrow.adbc.drivermanager.AdbcDriverManager; import org.apache.arrow.adbc.sql.SqlQuirks; import org.apache.arrow.memory.BufferAllocator; @@ -31,6 +32,11 @@ 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 a JDBC to ArrowType converter (type: {@link + * org.apache.arrow.adbc.driver.jdbc.adapter.JdbcToArrowTypeConverter}). + */ + public static final String PARAM_JDBC_TO_ARROW_TYPE = "adbc.jdbc.type_converter"; /** * A parameter for specifying a URI to connect to. * @@ -70,16 +76,19 @@ public AdbcDatabase open(Map parameters) throws AdbcException { throw AdbcException.invalidArgument( "[JDBC] Must provide both or neither of username and password"); } + JdbcToArrowTypeConverter typeConverter = + getParam(JdbcToArrowTypeConverter.class, parameters, PARAM_JDBC_TO_ARROW_TYPE); if (target != null) { dataSource = new UrlDataSource(target); } - if (dataSource != null) { - return new JdbcDataSourceDatabase(allocator, dataSource, username, password, quirks); + if (dataSource == null) { + throw AdbcException.invalidArgument( + "[JDBC] Must provide one of " + PARAM_URI + " and " + PARAM_DATASOURCE + " options"); } - throw AdbcException.invalidArgument( - "[JDBC] Must provide one of " + PARAM_URI + " and " + PARAM_DATASOURCE + " options"); + return new JdbcDataSourceDatabase( + allocator, dataSource, username, password, quirks, typeConverter); } private static T getParam(Class klass, Map parameters, String... choices) 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 new file mode 100644 index 0000000000..b6741a7cc6 --- /dev/null +++ b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/adapter/JdbcFieldInfoExtra.java @@ -0,0 +1,163 @@ +/* + * 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.adapter; + +import java.sql.ResultSet; +import java.sql.SQLException; +import org.apache.arrow.adapter.jdbc.JdbcFieldInfo; + +/** + * Information about a column from JDBC for inferring column type. + * + *

Extension of the upstream {@link JdbcFieldInfo} which lacks necessary fields for certain + * databases. + */ +public final class JdbcFieldInfoExtra { + final JdbcFieldInfo info; + final String typeName; + final int numPrecRadix; + final String remarks; + final String columnDef; + final int sqlDataType; + final int sqlDatetimeSub; + final int charOctetLength; + final int ordinalPosition; + + /** + * Create a JdbcFieldInfoExtra from the result of a {@link + * java.sql.DatabaseMetaData#getColumns(String, String, String, String)}. + * + * @param rs The result set. + */ + public JdbcFieldInfoExtra(ResultSet rs) throws SQLException { + final int dataType = rs.getInt("DATA_TYPE"); + this.typeName = rs.getString("TYPE_NAME"); + final int columnSize = rs.getInt("COLUMN_SIZE"); + final int decimalDigits = rs.getInt("DECIMAL_DIGITS"); + this.numPrecRadix = rs.getInt("NUM_PREC_RADIX"); + final int nullable = rs.getInt("NULLABLE"); + this.remarks = rs.getString("REMARKS"); + this.columnDef = rs.getString("COLUMN_DEF"); + this.sqlDataType = rs.getInt("SQL_DATA_TYPE"); + this.sqlDatetimeSub = rs.getInt("SQL_DATETIME_SUB"); + this.charOctetLength = rs.getInt("CHAR_OCTET_LENGTH"); + this.ordinalPosition = rs.getInt("ORDINAL_POSITION"); + + this.info = new JdbcFieldInfo(dataType, nullable, columnSize, decimalDigits); + } + + /** Get the base Arrow version. */ + public JdbcFieldInfo getFieldInfo() { + return info; + } + + /** The {@link java.sql.Types} type. */ + public int getJdbcType() { + return info.getJdbcType(); + } + + public String getTypeName() { + return typeName; + } + + public int getNumPrecRadix() { + return numPrecRadix; + } + + public String getRemarks() { + return remarks; + } + + public String getColumnDef() { + return columnDef; + } + + public int getSqlDataType() { + return sqlDataType; + } + + public int getSqlDatetimeSub() { + return sqlDatetimeSub; + } + + public int getCharOctetLength() { + return charOctetLength; + } + + public int getOrdinalPosition() { + return ordinalPosition; + } + + /** The nullability. */ + public int isNullable() { + return info.isNullable(); + } + + /** + * The numeric precision, for {@link java.sql.Types#NUMERIC} and {@link java.sql.Types#DECIMAL} + * types. + */ + public int getPrecision() { + return info.getPrecision(); + } + + /** + * The numeric scale, for {@link java.sql.Types#NUMERIC} and {@link java.sql.Types#DECIMAL} types. + */ + public int getScale() { + return info.getScale(); + } + + /** The column index for query column. */ + public int getColumn() { + return info.getColumn(); + } + + @Override + public String toString() { + return "JdbcFieldInfoExtra{" + + "dataType=" + + info.getJdbcType() + + ", typeName='" + + typeName + + '\'' + + ", columnSize=" + + info.getPrecision() + + ", decimalDigits=" + + info.getScale() + + ", numPrecRadix=" + + numPrecRadix + + ", nullable=" + + info.isNullable() + + ", remarks='" + + remarks + + '\'' + + ", columnDef='" + + columnDef + + '\'' + + ", sqlDataType=" + + sqlDataType + + ", sqlDatetimeSub=" + + sqlDatetimeSub + + ", charOctetLength=" + + charOctetLength + + ", ordinalPosition=" + + ordinalPosition + + '}'; + } +} diff --git a/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/adapter/JdbcToArrowTypeConverter.java b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/adapter/JdbcToArrowTypeConverter.java new file mode 100644 index 0000000000..95545f691d --- /dev/null +++ b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/adapter/JdbcToArrowTypeConverter.java @@ -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.adbc.driver.jdbc.adapter; + +import java.util.function.Function; +import org.apache.arrow.vector.types.pojo.ArrowType; + +@FunctionalInterface +public interface JdbcToArrowTypeConverter extends Function {} 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 new file mode 100644 index 0000000000..928bfdf8ee --- /dev/null +++ b/java/driver/jdbc/src/main/java/org/apache/arrow/adbc/driver/jdbc/adapter/JdbcToArrowTypeConverters.java @@ -0,0 +1,75 @@ +/* + * 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.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.pojo.ArrowType; + +public final class JdbcToArrowTypeConverters { + public static final JdbcToArrowTypeConverter MICROSOFT_SQL_SERVER = + JdbcToArrowTypeConverters::mssql; + public static final JdbcToArrowTypeConverter POSTGRESQL = JdbcToArrowTypeConverters::postgresql; + + private static ArrowType mssql(JdbcFieldInfoExtra field) { + switch (field.getJdbcType()) { + // DATETIME2 + // Precision is "100 nanoseconds" -> TimeUnit is NANOSECOND + case Types.TIMESTAMP: + return new ArrowType.Timestamp(TimeUnit.NANOSECOND, /*timezone*/ null); + // 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); + } + } + + private static ArrowType postgresql(JdbcFieldInfoExtra field) { + switch (field.getJdbcType()) { + case Types.TIMESTAMP: + { + int decimalDigits = field.getScale(); + final TimeUnit unit; + if (decimalDigits == 0) { + unit = TimeUnit.SECOND; + } else if (decimalDigits > 0 && decimalDigits <= 3) { + unit = TimeUnit.MILLISECOND; + } else if (decimalDigits > 0 && decimalDigits <= 6) { + unit = TimeUnit.MICROSECOND; + } else if (decimalDigits > 6) { + unit = TimeUnit.NANOSECOND; + } else { + // Negative precision? + return null; + } + if ("timestamptz".equals(field.getTypeName())) { + return new ArrowType.Timestamp(unit, "UTC"); + } else if ("timestamp".equals(field.getTypeName())) { + return new ArrowType.Timestamp(unit, /*timezone*/ null); + } + // Unknown type + return null; + } + default: + return JdbcToArrowUtils.getArrowTypeFromJdbcType(field.getFieldInfo(), /*calendar*/ null); + } + } +} diff --git a/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/AbstractSqlTypeTest.java b/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/AbstractSqlTypeTest.java new file mode 100644 index 0000000000..e231548b0d --- /dev/null +++ b/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/AbstractSqlTypeTest.java @@ -0,0 +1,223 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.arrow.adbc.driver.testsuite; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.stream.Collectors; +import org.apache.arrow.adbc.core.AdbcConnection; +import org.apache.arrow.adbc.core.AdbcDatabase; +import org.apache.arrow.adbc.core.AdbcStatement; +import org.apache.arrow.memory.BufferAllocator; +import org.apache.arrow.memory.RootAllocator; +import org.apache.arrow.util.AutoCloseables; +import org.apache.arrow.util.Preconditions; +import org.apache.arrow.vector.BigIntVector; +import org.apache.arrow.vector.DateDayVector; +import org.apache.arrow.vector.FieldVector; +import org.apache.arrow.vector.IntVector; +import org.apache.arrow.vector.TimeStampMicroTZVector; +import org.apache.arrow.vector.TimeStampMilliTZVector; +import org.apache.arrow.vector.TimeStampNanoTZVector; +import org.apache.arrow.vector.TimeStampSecTZVector; +import org.apache.arrow.vector.TimeStampVector; +import org.apache.arrow.vector.VarCharVector; +import org.apache.arrow.vector.ipc.ArrowReader; +import org.apache.arrow.vector.types.DateUnit; +import org.apache.arrow.vector.types.pojo.ArrowType; +import org.apache.arrow.vector.types.pojo.Schema; +import org.apache.arrow.vector.util.Text; +import org.assertj.core.api.InstanceOfAssertFactories; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** Test the Arrow type/value for each SQL type/value, for SQL-based databases. */ +public class AbstractSqlTypeTest { + /** Must be initialized by the subclass. */ + protected static SqlValidationQuirks quirks; + + protected BufferAllocator allocator; + protected AdbcDatabase database; + protected AdbcConnection connection; + protected AdbcStatement statement; + protected SqlTestUtil util; + + @BeforeEach + public void beforeEach() throws Exception { + Preconditions.checkNotNull(quirks, "Must initialize quirks in subclass with @BeforeAll"); + allocator = new RootAllocator(); + database = quirks.initDatabase(allocator); + connection = database.connect(); + util = new SqlTestUtil(quirks); + + final String setupSql = getResource(this.getClass().getSimpleName() + ".sql"); + try (final AdbcStatement stmt = connection.createStatement()) { + stmt.setSqlQuery(setupSql); + stmt.executeUpdate(); + } + } + + @AfterEach + public void afterEach() throws Exception { + AutoCloseables.close(connection, database, allocator); + } + + protected static String getResource(final String name) throws Exception { + try (InputStream is = + Thread.currentThread().getContextClassLoader().getResourceAsStream(name)) { + if (is == null) { + throw new RuntimeException("Could not find resource " + name); + } + try (InputStreamReader isr = new InputStreamReader(is); + BufferedReader reader = new BufferedReader(isr)) { + return reader.lines().collect(Collectors.joining(System.lineSeparator())); + } + } + } + + protected ArrowType assertValue( + final String column, final Class vectorType, final Object expectedValue) throws Exception { + try (final AdbcStatement stmt = connection.createStatement()) { + stmt.setSqlQuery("SELECT " + column + " FROM adbc_alltypes"); + try (AdbcStatement.QueryResult result = stmt.executeQuery()) { + ArrowReader reader = result.getReader(); + assertThat(reader.loadNextBatch()).isTrue(); + assertThat(reader.getVectorSchemaRoot().getRowCount()).isEqualTo(2); + FieldVector vector = reader.getVectorSchemaRoot().getVector(0); + assertThat(vector).isInstanceOf(vectorType); + assertThat(vector.getObject(0)).isEqualTo(expectedValue); + assertThat(vector.getObject(1)).isNull(); + return vector.getField().getType(); + } + } + } + + @Test + protected void bigintType() throws Exception { + final Schema schema = connection.getTableSchema(null, null, "adbc_alltypes"); + assertThat(schema.findField("bigint_t").getType()) + .asInstanceOf(InstanceOfAssertFactories.type(ArrowType.Int.class)) + .extracting(ArrowType.Int::getBitWidth, ArrowType.Int::getIsSigned) + .containsExactly(64, true); + } + + @Test + protected void bigintValue() throws Exception { + assertValue("bigint_t", BigIntVector.class, 42L); + } + + @Test + protected void dateType() throws Exception { + final Schema schema = connection.getTableSchema(null, null, "adbc_alltypes"); + assertThat(schema.findField("date_t").getType()).isEqualTo(new ArrowType.Date(DateUnit.DAY)); + } + + @Test + protected void dateValue() throws Exception { + assertValue("date_t", DateDayVector.class, 10957); + } + + @Test + protected void intType() throws Exception { + final Schema schema = connection.getTableSchema(null, null, "adbc_alltypes"); + assertThat(schema.findField("int_t").getType()) + .asInstanceOf(InstanceOfAssertFactories.type(ArrowType.Int.class)) + .extracting(ArrowType.Int::getBitWidth, ArrowType.Int::getIsSigned) + .containsExactly(32, true); + } + + @Test + protected void intValue() throws Exception { + assertValue("int_t", IntVector.class, 42); + } + + @Test + protected void textType() throws Exception { + final Schema schema = connection.getTableSchema(null, null, "adbc_alltypes"); + assertThat(schema.findField("text_t").getType()).isInstanceOf(ArrowType.Utf8.class); + } + + @Test + protected void textValue() throws Exception { + assertValue("text_t", VarCharVector.class, new Text("foo")); + } + + @Test + protected void timestampWithoutTimeZoneType() throws Exception { + final Schema schema = connection.getTableSchema(null, null, "adbc_alltypes"); + assertThat(schema.findField("timestamp_without_time_zone_t").getType()) + .isEqualTo(new ArrowType.Timestamp(quirks.defaultTimestampUnit(), null)); + } + + @Test + protected void timestampWithoutTimeZoneValue() throws Exception { + final ArrowType type; + switch (quirks.defaultTimestampUnit()) { + case SECOND: + type = assertValue("timestamp_without_time_zone_t", TimeStampVector.class, 946_782_245L); + break; + case MILLISECOND: + type = + assertValue("timestamp_without_time_zone_t", TimeStampVector.class, 946_782_245_123L); + break; + case MICROSECOND: + type = + assertValue( + "timestamp_without_time_zone_t", TimeStampVector.class, 946_782_245_123_000L); + break; + case NANOSECOND: + type = + assertValue( + "timestamp_without_time_zone_t", TimeStampVector.class, 946_782_245_123_000_000L); + break; + default: + throw new UnsupportedOperationException(); + } + assertThat(type).isEqualTo(new ArrowType.Timestamp(quirks.defaultTimestampUnit(), null)); + } + + @Test + protected void timestampWithTimeZoneType() throws Exception { + final Schema schema = connection.getTableSchema(null, null, "adbc_alltypes"); + assertThat(schema.findField("timestamp_with_time_zone_t").getType()) + .isEqualTo(new ArrowType.Timestamp(quirks.defaultTimestampUnit(), "UTC")); + } + + @Test + protected void timestampWithTimeZoneValue() throws Exception { + switch (quirks.defaultTimestampUnit()) { + case SECOND: + assertValue("timestamp_with_time_zone_t", TimeStampSecTZVector.class, 946_760_645L); + break; + case MILLISECOND: + assertValue("timestamp_with_time_zone_t", TimeStampMilliTZVector.class, 946_760_645_123L); + break; + case MICROSECOND: + assertValue( + "timestamp_with_time_zone_t", TimeStampMicroTZVector.class, 946_760_645_123_000L); + break; + case NANOSECOND: + assertValue( + "timestamp_with_time_zone_t", TimeStampNanoTZVector.class, 946_760_645_123_000_000L); + break; + } + } +} diff --git a/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/SqlValidationQuirks.java b/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/SqlValidationQuirks.java index 895c3a0f3a..a8d259cfb7 100644 --- a/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/SqlValidationQuirks.java +++ b/java/driver/validation/src/main/java/org/apache/arrow/adbc/driver/testsuite/SqlValidationQuirks.java @@ -22,6 +22,7 @@ import org.apache.arrow.adbc.core.AdbcDatabase; import org.apache.arrow.adbc.core.AdbcException; import org.apache.arrow.memory.BufferAllocator; +import org.apache.arrow.vector.types.TimeUnit; /** Account for driver/vendor-specific quirks in implementing validation tests. */ public abstract class SqlValidationQuirks { @@ -85,4 +86,8 @@ public String generateAddForeignKeyQuery( + caseFoldColumnName(referenceColumn) + ") "; } + + public TimeUnit defaultTimestampUnit() { + return TimeUnit.MILLISECOND; + } } diff --git a/java/pom.xml b/java/pom.xml index 216ace5c0a..4b1d09d58a 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -85,6 +85,7 @@ driver/flight-sql-validation driver/jdbc driver/jdbc-validation-derby + driver/jdbc-validation-mssqlserver driver/jdbc-validation-postgresql driver/validation driver-manager