diff --git a/java/adapter/jdbc/src/main/java/org/apache/arrow/adapter/jdbc/JdbcToArrow.java b/java/adapter/jdbc/src/main/java/org/apache/arrow/adapter/jdbc/JdbcToArrow.java index 090bd612ade3e..0d464961afd83 100644 --- a/java/adapter/jdbc/src/main/java/org/apache/arrow/adapter/jdbc/JdbcToArrow.java +++ b/java/adapter/jdbc/src/main/java/org/apache/arrow/adapter/jdbc/JdbcToArrow.java @@ -94,8 +94,9 @@ public static VectorSchemaRoot sqlToArrow(Connection connection, String query, B Preconditions.checkArgument(query != null && query.length() > 0, "SQL query can not be null or empty"); Preconditions.checkNotNull(allocator, "Memory allocator object can not be null"); - return sqlToArrow(connection, query, allocator, - Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.ROOT), false); + JdbcToArrowConfig config = + new JdbcToArrowConfig(allocator, Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.ROOT), false); + return sqlToArrow(connection, query, config); } /** @@ -122,36 +123,30 @@ public static VectorSchemaRoot sqlToArrow( Preconditions.checkNotNull(allocator, "Memory allocator object can not be null"); Preconditions.checkNotNull(calendar, "Calendar object can not be null"); - return sqlToArrow(connection, query, allocator, calendar, false); + return sqlToArrow(connection, query, new JdbcToArrowConfig(allocator, calendar)); } /** * For the given SQL query, execute and fetch the data from Relational DB and convert it to Arrow objects. * - * @param connection Database connection to be used. This method will not close the passed connection object. - * Since the caller has passed the connection object it's the responsibility of the caller - * to close or return the connection to the pool. - * @param query The DB Query to fetch the data. - * @param allocator Memory allocator - * @param calendar Calendar object to use to handle Date, Time and Timestamp datasets. - * @param includeMetadata Whether to include column information in the schema field metadata. + * @param connection Database connection to be used. This method will not close the passed connection object. + * Since the caller has passed the connection object it's the responsibility of the caller + * to close or return the connection to the pool. + * @param query The DB Query to fetch the data. + * @param config Configuration * @return Arrow Data Objects {@link VectorSchemaRoot} * @throws SQLException Propagate any SQL Exceptions to the caller after closing any resources opened such as * ResultSet and Statement objects. */ - public static VectorSchemaRoot sqlToArrow( - Connection connection, - String query, - BaseAllocator allocator, - Calendar calendar, - boolean includeMetadata) throws SQLException, IOException { + public static VectorSchemaRoot sqlToArrow(Connection connection, String query, JdbcToArrowConfig config) + throws SQLException, IOException { Preconditions.checkNotNull(connection, "JDBC connection object can not be null"); Preconditions.checkArgument(query != null && query.length() > 0, "SQL query can not be null or empty"); - Preconditions.checkNotNull(allocator, "Memory allocator object can not be null"); - Preconditions.checkNotNull(calendar, "Calendar object can not be null"); + Preconditions.checkNotNull(config, "The configuration cannot be null"); + Preconditions.checkArgument(config.isValid(), "The configuration must be valid"); try (Statement stmt = connection.createStatement()) { - return sqlToArrow(stmt.executeQuery(query), allocator, calendar, includeMetadata); + return sqlToArrow(stmt.executeQuery(query), config); } } @@ -182,7 +177,9 @@ public static VectorSchemaRoot sqlToArrow(ResultSet resultSet, BaseAllocator all Preconditions.checkNotNull(resultSet, "JDBC ResultSet object can not be null"); Preconditions.checkNotNull(allocator, "Memory Allocator object can not be null"); - return sqlToArrow(resultSet, allocator, Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.ROOT)); + JdbcToArrowConfig config = + new JdbcToArrowConfig(allocator, Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.ROOT)); + return sqlToArrow(resultSet, config); } /** @@ -197,10 +194,7 @@ public static VectorSchemaRoot sqlToArrow(ResultSet resultSet, Calendar calendar Preconditions.checkNotNull(resultSet, "JDBC ResultSet object can not be null"); Preconditions.checkNotNull(calendar, "Calendar object can not be null"); - RootAllocator rootAllocator = new RootAllocator(Integer.MAX_VALUE); - VectorSchemaRoot root = sqlToArrow(resultSet, rootAllocator, calendar, false); - - return root; + return sqlToArrow(resultSet, new JdbcToArrowConfig(new RootAllocator(Integer.MAX_VALUE), calendar)); } /** @@ -221,32 +215,26 @@ public static VectorSchemaRoot sqlToArrow( Preconditions.checkNotNull(allocator, "Memory Allocator object can not be null"); Preconditions.checkNotNull(calendar, "Calendar object can not be null"); - return sqlToArrow(resultSet, allocator, calendar, false); + return sqlToArrow(resultSet, new JdbcToArrowConfig(allocator, calendar)); } /** * For the given JDBC {@link ResultSet}, fetch the data from Relational DB and convert it to Arrow objects. * - * @param resultSet ResultSet to use to fetch the data from underlying database - * @param allocator Memory allocator to use. - * @param calendar Calendar instance to use for Date, Time and Timestamp datasets. - * @param includeMetadata Whether to include column information in the schema field metadata. + * @param resultSet ResultSet to use to fetch the data from underlying database + * @param config Configuration of the conversion from JDBC to Arrow. * @return Arrow Data Objects {@link VectorSchemaRoot} * @throws SQLException on error */ - public static VectorSchemaRoot sqlToArrow( - ResultSet resultSet, - BaseAllocator allocator, - Calendar calendar, - boolean includeMetadata) + public static VectorSchemaRoot sqlToArrow(ResultSet resultSet, JdbcToArrowConfig config) throws SQLException, IOException { Preconditions.checkNotNull(resultSet, "JDBC ResultSet object can not be null"); - Preconditions.checkNotNull(allocator, "Memory Allocator object can not be null"); - Preconditions.checkNotNull(calendar, "Calendar object can not be null"); + Preconditions.checkNotNull(config, "The configuration cannot be null"); + Preconditions.checkArgument(config.isValid(), "The configuration must be valid"); VectorSchemaRoot root = VectorSchemaRoot.create( - JdbcToArrowUtils.jdbcToArrowSchema(resultSet.getMetaData(), calendar, includeMetadata), allocator); - JdbcToArrowUtils.jdbcToArrowVectors(resultSet, root, calendar); + JdbcToArrowUtils.jdbcToArrowSchema(resultSet.getMetaData(), config.getCalendar(), config.includeMetadata()), config.getAllocator()); + JdbcToArrowUtils.jdbcToArrowVectors(resultSet, root, config.getCalendar()); return root; } } diff --git a/java/adapter/jdbc/src/main/java/org/apache/arrow/adapter/jdbc/JdbcToArrowConfig.java b/java/adapter/jdbc/src/main/java/org/apache/arrow/adapter/jdbc/JdbcToArrowConfig.java new file mode 100644 index 0000000000000..ab78ae5ecd859 --- /dev/null +++ b/java/adapter/jdbc/src/main/java/org/apache/arrow/adapter/jdbc/JdbcToArrowConfig.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.arrow.adapter.jdbc; + +import java.util.Calendar; + +import org.apache.arrow.memory.BaseAllocator; + +import com.google.common.base.Preconditions; + +/** + * This class configures the JDBC-to-Arrow conversion process. + *

+ * The allocator is used to construct the {@link org.apache.arrow.vector.VectorSchemaRoot}, + * and the calendar is used to define the time zone of any {@link org.apahe.arrow.vector.pojo.ArrowType.Timestamp} + * fields that are created during the conversion. + *

+ *

+ * Neither field may be null. + *

+ */ +public final class JdbcToArrowConfig { + private Calendar calendar; + private BaseAllocator allocator; + private boolean includeMetadata; + + /** + * Constructs a new configuration from the provided allocator and calendar. The allocator + * is used when constructing the Arrow vectors from the ResultSet, and the calendar is used to define + * Arrow Timestamp fields, and to read time-based fields from the JDBC ResultSet. + * + * @param allocator The memory allocator to construct the Arrow vectors with. + * @param calendar The calendar to use when constructing Timestamp fields and reading time-based results. + */ + public JdbcToArrowConfig(BaseAllocator allocator, Calendar calendar) { + Preconditions.checkNotNull(allocator, "Memory allocator cannot be null"); + Preconditions.checkNotNull(calendar, "Calendar object can not be null"); + + this.allocator = allocator; + this.calendar = calendar; + this.includeMetadata = false; + } + + public JdbcToArrowConfig(BaseAllocator allocator, Calendar calendar, boolean includeMetadata) { + this(allocator, calendar); + this.includeMetadata = includeMetadata; + } + + /** + * The calendar to use when defining Arrow Timestamp fields + * and retrieving time-based fields from the database. + * @return the calendar. + */ + public Calendar getCalendar() { + return calendar; + } + + /** + * Sets the {@link Calendar} to use when constructing timestamp fields in the + * Arrow schema, and reading time-based fields from the JDBC ResultSet. + * + * @param calendar the calendar to set. + * @exception NullPointerExeption if calendar is null. + */ + public JdbcToArrowConfig setCalendar(Calendar calendar) { + Preconditions.checkNotNull(calendar, "Calendar object can not be null"); + this.calendar = calendar; + return this; + } + + /** + * The Arrow memory allocator. + * @return the allocator. + */ + public BaseAllocator getAllocator() { + return allocator; + } + + /** + * Sets the memory allocator to use when construting the Arrow vectors from the ResultSet. + * + * @param allocator the allocator to set. + * @exception NullPointerException if allocator is null. + */ + public JdbcToArrowConfig setAllocator(BaseAllocator allocator) { + Preconditions.checkNotNull(allocator, "Memory allocator cannot be null"); + this.allocator = allocator; + return this; + } + + public boolean includeMetadata() { + return includeMetadata; + } + + /** + * Whether this configuration is valid. The configuration is valid when: + * + * + * @return Whether this configuration is valid. + */ + public boolean isValid() { + return (calendar != null) && (allocator != null); + } +} diff --git a/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/AbstractJdbcToArrowTest.java b/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/AbstractJdbcToArrowTest.java index a147babc4524d..b1a93291d2be7 100644 --- a/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/AbstractJdbcToArrowTest.java +++ b/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/AbstractJdbcToArrowTest.java @@ -45,7 +45,7 @@ public abstract class AbstractJdbcToArrowTest { * @return Table object * @throws IOException on error */ - protected static Table getTable(String ymlFilePath, Class clss) throws IOException { + protected static Table getTable(String ymlFilePath, @SuppressWarnings("rawtypes") Class clss) throws IOException { return new ObjectMapper(new YAMLFactory()).readValue( clss.getClassLoader().getResourceAsStream(ymlFilePath), Table.class); } @@ -94,7 +94,7 @@ public void destroy() throws SQLException { * @throws ClassNotFoundException on error * @throws IOException on error */ - public static Object[][] prepareTestData(String[] testFiles, Class clss) + public static Object[][] prepareTestData(String[] testFiles, @SuppressWarnings("rawtypes") Class clss) throws SQLException, ClassNotFoundException, IOException { Object[][] tableArr = new Object[testFiles.length][]; int i = 0; diff --git a/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/JdbcToArrowConfigTest.java b/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/JdbcToArrowConfigTest.java new file mode 100644 index 0000000000000..116fa46fd548d --- /dev/null +++ b/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/JdbcToArrowConfigTest.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.arrow.adapter.jdbc; + +import static org.junit.Assert.*; + +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; + +import org.apache.arrow.memory.BaseAllocator; +import org.apache.arrow.memory.RootAllocator; +import org.junit.Test; + +public class JdbcToArrowConfigTest { + + private static final RootAllocator allocator = new RootAllocator(Integer.MAX_VALUE); + private static final Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.ROOT); + + @Test(expected = NullPointerException.class) + public void testNullArguments() { + new JdbcToArrowConfig(null, null); + } + + @Test(expected = NullPointerException.class) + public void testNullCalendar() { + new JdbcToArrowConfig(allocator, null); + } + + @Test(expected = NullPointerException.class) + public void testNullAllocator() { + new JdbcToArrowConfig(null, calendar); + } + + @Test(expected = NullPointerException.class) + public void testSetNullAllocator() { + JdbcToArrowConfig config = new JdbcToArrowConfig(allocator, calendar); + config.setAllocator(null); + } + + @Test(expected = NullPointerException.class) + public void testSetNullCalendar() { + JdbcToArrowConfig config = new JdbcToArrowConfig(allocator, calendar); + config.setCalendar(null); + } + + @Test + public void testConfig() { + JdbcToArrowConfig config = new JdbcToArrowConfig(allocator, calendar); + assertTrue(config.isValid()); + assertTrue(allocator == config.getAllocator()); + assertTrue(calendar == config.getCalendar()); + + Calendar newCalendar = Calendar.getInstance(); + BaseAllocator newAllocator = new RootAllocator(Integer.SIZE); + + config.setAllocator(newAllocator).setCalendar(newCalendar); + + assertTrue(config.isValid()); + assertTrue(newAllocator == config.getAllocator()); + assertTrue(newCalendar == config.getCalendar()); + } +} diff --git a/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/h2/JdbcToArrowCharSetTest.java b/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/h2/JdbcToArrowCharSetTest.java index c7dff431da650..f77ead43d6919 100644 --- a/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/h2/JdbcToArrowCharSetTest.java +++ b/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/h2/JdbcToArrowCharSetTest.java @@ -31,6 +31,7 @@ import org.apache.arrow.adapter.jdbc.AbstractJdbcToArrowTest; import org.apache.arrow.adapter.jdbc.JdbcToArrow; +import org.apache.arrow.adapter.jdbc.JdbcToArrowConfig; import org.apache.arrow.adapter.jdbc.Table; import org.apache.arrow.memory.RootAllocator; import org.apache.arrow.vector.VarCharVector; @@ -116,6 +117,13 @@ public void testJdbcToArroValues() throws SQLException, IOException { new RootAllocator(Integer.MAX_VALUE))); testDataSets(JdbcToArrow.sqlToArrow(conn.createStatement().executeQuery(table.getQuery()), Calendar.getInstance())); + testDataSets(JdbcToArrow.sqlToArrow( + conn.createStatement().executeQuery(table.getQuery()), + new JdbcToArrowConfig(new RootAllocator(Integer.MAX_VALUE), Calendar.getInstance()))); + testDataSets(JdbcToArrow.sqlToArrow( + conn, + table.getQuery(), + new JdbcToArrowConfig(new RootAllocator(Integer.MAX_VALUE), Calendar.getInstance()))); } /** diff --git a/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/h2/JdbcToArrowDataTypesTest.java b/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/h2/JdbcToArrowDataTypesTest.java index f6cd7645e0cac..ef08133de9395 100644 --- a/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/h2/JdbcToArrowDataTypesTest.java +++ b/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/h2/JdbcToArrowDataTypesTest.java @@ -40,6 +40,7 @@ import org.apache.arrow.adapter.jdbc.AbstractJdbcToArrowTest; import org.apache.arrow.adapter.jdbc.JdbcToArrow; +import org.apache.arrow.adapter.jdbc.JdbcToArrowConfig; import org.apache.arrow.adapter.jdbc.Table; import org.apache.arrow.memory.RootAllocator; import org.apache.arrow.vector.BigIntVector; @@ -142,6 +143,13 @@ public void testJdbcToArroValues() throws SQLException, IOException { testDataSets(JdbcToArrow.sqlToArrow(conn.createStatement().executeQuery(table.getQuery()), new RootAllocator(Integer.MAX_VALUE))); testDataSets(JdbcToArrow.sqlToArrow(conn.createStatement().executeQuery(table.getQuery()), Calendar.getInstance())); + testDataSets(JdbcToArrow.sqlToArrow( + conn.createStatement().executeQuery(table.getQuery()), + new JdbcToArrowConfig(new RootAllocator(Integer.MAX_VALUE), Calendar.getInstance()))); + testDataSets(JdbcToArrow.sqlToArrow( + conn, + table.getQuery(), + new JdbcToArrowConfig(new RootAllocator(Integer.MAX_VALUE), Calendar.getInstance()))); } /** diff --git a/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/h2/JdbcToArrowNullTest.java b/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/h2/JdbcToArrowNullTest.java index 7933732f014b0..0ebfd50868480 100644 --- a/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/h2/JdbcToArrowNullTest.java +++ b/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/h2/JdbcToArrowNullTest.java @@ -27,6 +27,7 @@ import org.apache.arrow.adapter.jdbc.AbstractJdbcToArrowTest; import org.apache.arrow.adapter.jdbc.JdbcToArrow; +import org.apache.arrow.adapter.jdbc.JdbcToArrowConfig; import org.apache.arrow.adapter.jdbc.Table; import org.apache.arrow.memory.RootAllocator; import org.apache.arrow.vector.BigIntVector; @@ -99,6 +100,13 @@ public void testJdbcToArroValues() throws SQLException, IOException { testDataSets(JdbcToArrow.sqlToArrow(conn.createStatement().executeQuery(table.getQuery()), new RootAllocator(Integer.MAX_VALUE))); testDataSets(JdbcToArrow.sqlToArrow(conn.createStatement().executeQuery(table.getQuery()), Calendar.getInstance())); + testDataSets(JdbcToArrow.sqlToArrow( + conn.createStatement().executeQuery(table.getQuery()), + new JdbcToArrowConfig(new RootAllocator(Integer.MAX_VALUE), Calendar.getInstance()))); + testDataSets(JdbcToArrow.sqlToArrow( + conn, + table.getQuery(), + new JdbcToArrowConfig(new RootAllocator(Integer.MAX_VALUE), Calendar.getInstance()))); } diff --git a/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/h2/JdbcToArrowTest.java b/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/h2/JdbcToArrowTest.java index 4cbfeafb0a531..c1b1f78ac2510 100644 --- a/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/h2/JdbcToArrowTest.java +++ b/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/h2/JdbcToArrowTest.java @@ -48,6 +48,7 @@ import org.apache.arrow.adapter.jdbc.AbstractJdbcToArrowTest; import org.apache.arrow.adapter.jdbc.JdbcToArrow; +import org.apache.arrow.adapter.jdbc.JdbcToArrowConfig; import org.apache.arrow.adapter.jdbc.Table; import org.apache.arrow.memory.RootAllocator; import org.apache.arrow.vector.BigIntVector; @@ -133,6 +134,13 @@ public void testJdbcToArroValues() throws SQLException, IOException { new RootAllocator(Integer.MAX_VALUE))); testDataSets(JdbcToArrow.sqlToArrow(conn.createStatement().executeQuery(table.getQuery()), Calendar.getInstance())); + testDataSets(JdbcToArrow.sqlToArrow( + conn.createStatement().executeQuery(table.getQuery()), + new JdbcToArrowConfig(new RootAllocator(Integer.MAX_VALUE), Calendar.getInstance()))); + testDataSets(JdbcToArrow.sqlToArrow( + conn, + table.getQuery(), + new JdbcToArrowConfig(new RootAllocator(Integer.MAX_VALUE), Calendar.getInstance()))); } /** diff --git a/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/h2/JdbcToArrowTimeZoneTest.java b/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/h2/JdbcToArrowTimeZoneTest.java index 93dc10477f697..09ec25bc78145 100644 --- a/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/h2/JdbcToArrowTimeZoneTest.java +++ b/java/adapter/jdbc/src/test/java/org/apache/arrow/adapter/jdbc/h2/JdbcToArrowTimeZoneTest.java @@ -30,6 +30,7 @@ import org.apache.arrow.adapter.jdbc.AbstractJdbcToArrowTest; import org.apache.arrow.adapter.jdbc.JdbcToArrow; +import org.apache.arrow.adapter.jdbc.JdbcToArrowConfig; import org.apache.arrow.adapter.jdbc.Table; import org.apache.arrow.memory.RootAllocator; import org.apache.arrow.vector.DateMilliVector; @@ -105,6 +106,17 @@ public void testJdbcToArroValues() throws SQLException, IOException { new RootAllocator(Integer.MAX_VALUE), Calendar.getInstance(TimeZone.getTimeZone(table.getTimezone())))); testDataSets(JdbcToArrow.sqlToArrow(conn.createStatement().executeQuery(table.getQuery()), Calendar.getInstance(TimeZone.getTimeZone(table.getTimezone())))); + testDataSets(JdbcToArrow.sqlToArrow( + conn.createStatement().executeQuery(table.getQuery()), + new JdbcToArrowConfig( + new RootAllocator(Integer.MAX_VALUE), + Calendar.getInstance(TimeZone.getTimeZone(table.getTimezone()))))); + testDataSets(JdbcToArrow.sqlToArrow( + conn, + table.getQuery(), + new JdbcToArrowConfig( + new RootAllocator(Integer.MAX_VALUE), + Calendar.getInstance(TimeZone.getTimeZone(table.getTimezone()))))); } /**