-
Notifications
You must be signed in to change notification settings - Fork 4.2k
ARROW-1780 - JDBC Adapter to convert Relational Data objects to Arrow Data Format Vector Objects #1759
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ARROW-1780 - JDBC Adapter to convert Relational Data objects to Arrow Data Format Vector Objects #1759
Changes from 68 commits
1b2d27f
c482b50
e462f80
89d02bf
c9d2727
b003abb
134e339
333bc01
32fa968
1e5c584
127b4fb
8e2b7f5
772ae96
a556695
e3c490d
df444ad
15f281f
a32f788
1b4175f
4c706f5
9ead27d
73b0198
c97e910
9459221
5c1f5f2
f76ac48
a4d2b32
5fb446c
7f70a67
c2ac474
f834c77
e1c1078
378f660
ba8156d
fdb9d71
30a5341
ff4c79b
646eba6
8c21902
f5e95d2
1dd9079
5c56cf7
eff855a
230ba4a
f955614
6911c40
f624bce
3bb513a
8e25761
a007f31
6641ad4
bffbe13
29b40cd
f2c4ae0
d0e55f9
f5f6efe
8a218c4
1441d3b
3b99621
5a59278
a6cc2fe
76c1efb
7f8f613
1d0f29b
8b52507
654325b
63f7735
68c3a61
69d5202
1400fc8
fe1f27d
3b4ebc5
c5b3f56
00d2f11
6556a44
8b23396
e55cb48
fbd84fb
2c56d59
b8dce96
85a2354
e1ed84d
f757164
540f69b
66f19e5
4830800
675a570
2b6b720
32bfb1f
bafbd1a
09b526e
7320cdc
0707a0c
af43943
f3b923c
6578d17
9a6aa9a
501aacc
d1b0992
b95e878
da1227d
857a4b4
6b0b00e
7cc649c
73d1097
054d1eb
75d5d1f
c2ba1ee
e2ba906
acda6e3
f06d85e
4bfbfe6
4329c4b
840970c
2069103
95f8e23
a68cc23
60314c6
32ba284
aa6eb92
3a2b312
02952c4
eac219a
2129ff9
9990616
52c0047
16cda22
f685b93
dcaa74a
239fca6
13ec352
12780ed
0c78755
67593cd
d260342
6254260
c9b22fe
fa65a31
9a2f463
7125d6e
b124ece
654a5e4
e189b14
c66f4a2
abaea4e
16d8ec1
25eadcf
dd1ffa4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -74,3 +74,5 @@ c_glib/doc/reference/gtk-doc.make | |
| *.svg | ||
| *.devhelp2 | ||
| *.scss | ||
| *.yml | ||
| *.properties | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <!-- 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. --> | ||
|
|
||
| <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | ||
|
|
||
| <modelVersion>4.0.0</modelVersion> | ||
| <groupId>org.apache.arrow.adapter.jdbc</groupId> | ||
| <artifactId>arrow-jdbc</artifactId> | ||
| <packaging>jar</packaging> | ||
| <version>0.10.0-SNAPSHOT</version> | ||
| <name>Arrow JDBC Adapter</name> | ||
| <url>http://maven.apache.org</url> | ||
| <dependencies> | ||
|
|
||
| <!-- https://mvnrepository.com/artifact/org.apache.arrow/arrow-memory --> | ||
| <dependency> | ||
| <groupId>org.apache.arrow</groupId> | ||
| <artifactId>arrow-memory</artifactId> | ||
| <version>0.9.0-SNAPSHOT</version> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
| </dependency> | ||
| <!-- https://mvnrepository.com/artifact/org.apache.arrow/arrow-vector --> | ||
| <dependency> | ||
| <groupId>org.apache.arrow</groupId> | ||
| <artifactId>arrow-vector</artifactId> | ||
| <version>0.9.0-SNAPSHOT</version> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See above |
||
| </dependency> | ||
|
|
||
| <!-- Test --> | ||
| <dependency> | ||
| <groupId>junit</groupId> | ||
| <artifactId>junit</artifactId> | ||
| <version>4.11</version> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. replace with ${dep.junit.version} |
||
| <scope>test</scope> | ||
| </dependency> | ||
| <!-- https://mvnrepository.com/artifact/com.h2database/h2 --> | ||
| <dependency> | ||
| <groupId>com.h2database</groupId> | ||
| <artifactId>h2</artifactId> | ||
| <version>1.4.196</version> | ||
| <scope>test</scope> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>com.fasterxml.jackson.dataformat</groupId> | ||
| <artifactId>jackson-dataformat-yaml</artifactId> | ||
| <version>2.3.0</version> | ||
| <scope>test</scope> | ||
| </dependency> | ||
|
|
||
|
|
||
| </dependencies> | ||
|
|
||
| <build> | ||
| <plugins> | ||
| <plugin> | ||
| <groupId>org.apache.maven.plugins</groupId> | ||
| <artifactId>maven-surefire-plugin</artifactId> | ||
| <configuration> | ||
| <systemPropertyVariables> | ||
| <user.timezone>UTC</user.timezone> | ||
| </systemPropertyVariables> | ||
| </configuration> | ||
|
|
||
| </plugin> | ||
| </plugins> | ||
| </build> | ||
| </project> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| /** | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.apache.arrow.adapter.jdbc; | ||
|
|
||
| import org.apache.arrow.vector.VectorSchemaRoot; | ||
|
|
||
| import java.sql.Connection; | ||
|
|
||
| /** | ||
| * Class to fetch data from a given database table where user can specify columns to fetch | ||
| * along with limit and offset parameters. | ||
| * | ||
| * The object of this class is returned by invoking method jdbcArrowDataFetcher(Connection connection, String tableName) | ||
| * from {@link JdbcToArrow} class. Caller can use this object to fetch data repetitively based on the | ||
| * data fetch requirement and can implement pagination like functionality. | ||
| * | ||
| * This class doesn't hold any open connections to database but simply executes the "select" query everytime with | ||
| * the necessary limit and offset parameters. | ||
| * | ||
| * @since 0.10.0 | ||
| * @see JdbcToArrow | ||
| */ | ||
| public class ArrowDataFetcher { | ||
|
|
||
| private static final String all_columns_query = "select * from %s limit %d offset %d"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that's assuming that this is supported by all SQL dialects (which is not the case).
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this is true, my bad! I was aware of this and should have thought through before implementing this. What I am thinking here now is to come up with a Java enum for all the databases and maintain a map or constant string with (limit/offset) query specific to each database. This way I can support - ORACLE_12C, MYSQL, DB2, SQL_SERVER_2012, SQL_SERVER_2008, POSTGRESQL, H2, SQLDB, INGRES, DERBY, SQLITE, CUBRID, SYBASE_ASE, SYBASE_SQL_ANYWHERE, FIREBIRD. But the only problem here is in writing test cases. What do you think about this approach and if this is okay, how we can go about testing the code for each database? |
||
| private static final String custom_columns_query = "select %s from %s limit %d offset %d"; | ||
| private Connection connection; | ||
| private String tableName; | ||
|
|
||
| /** | ||
| * Constructor | ||
| * @param connection | ||
| * @param tableName | ||
| */ | ||
| public ArrowDataFetcher(Connection connection, String tableName) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not wrapping a ResultSet instead?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @laurentgo Can you elaborate it a bit - what do you mean by wrapping a ResultSet?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My suggestion is to let the user do the query thing (get a connection, create the statement and execute it), and use the resulting {{ResultSet}} to do the Arrow conversation (and hopefully no need to deal with different dialects and other stuff)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aah, okay, that makes sense. I think in that case, we don't even need to worry about various databases and also testing against each of those.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's my belief too... |
||
| this.connection = connection; | ||
| this.tableName = tableName; | ||
| } | ||
|
|
||
| /** | ||
| * Fetch the data from underlying table with the given limit and offset and for passed column names. | ||
| * | ||
| * @param offset | ||
| * @param limit | ||
| * @param columns | ||
| * @return | ||
| * @throws Exception | ||
| */ | ||
| public VectorSchemaRoot fetch(int offset, int limit, String... columns) throws Exception { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exception is very broad, should we try to be more specific?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
| assert columns != null && columns.length > 0 : "columns can't be empty!"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shouldn't
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you want me to use any particular Preconditions API such Guava's com.google.common.base.Preconditions? As "assert" is doing pretty much the same thing except it throws AssertionError as opposed to IllegalArgumentException.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Apart from the semantic difference (assertion are usually internal precondition of how the class behave, to help debugging), asserts are only turned on if enabled at the JDK level (using the -ea flag). Whereas preconditions are always checked.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea, I here you!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
| assert limit > 0 : "limit needs to be greater that 0"; | ||
| assert offset >= 0 : "offset needs to be greater than or equal to 0"; | ||
|
|
||
| return JdbcToArrow.sqlToArrow(connection, | ||
| String.format(custom_columns_query, commaSeparatedQueryColumns(columns), | ||
| tableName, limit, offset)); | ||
| } | ||
|
|
||
| /** | ||
| * Fetch the data from underlying table with the given limit and offset and for all the columns. | ||
| * @param offset | ||
| * @param limit | ||
| * @return | ||
| * @throws Exception | ||
| */ | ||
| public VectorSchemaRoot fetch(int offset, int limit) throws Exception { | ||
| assert limit > 0 : "limit needs to be greater that 0"; | ||
| assert offset >= 0 : "offset needs to be greater than or equal to 0"; | ||
|
|
||
| return JdbcToArrow.sqlToArrow(connection, String.format(all_columns_query, tableName, limit, offset)); | ||
| } | ||
|
|
||
| public static String commaSeparatedQueryColumns(String... columns) { | ||
| assert columns != null && columns.length > 0 : "columns can't be empty!"; | ||
|
|
||
| StringBuilder columnBuilder = new StringBuilder(); | ||
| boolean insertComma = false; | ||
| for (String s: columns) { | ||
| if (insertComma) { | ||
| columnBuilder.append(','); | ||
| } | ||
| columnBuilder.append(' ').append(s); | ||
| insertComma = true; | ||
| } | ||
| columnBuilder.append(' '); | ||
| return columnBuilder.toString(); | ||
| } | ||
|
|
||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| /** | ||
| * Licensed to the Apache Software Foundation (ASF) under one | ||
| * or more contributor license agreements. See the NOTICE file | ||
| * distributed with this work for additional information | ||
| * regarding copyright ownership. The ASF licenses this file | ||
| * to you under the Apache License, Version 2.0 (the | ||
| * "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.apache.arrow.adapter.jdbc; | ||
|
|
||
| import org.apache.arrow.memory.RootAllocator; | ||
| import org.apache.arrow.vector.VectorSchemaRoot; | ||
|
|
||
| import java.sql.*; | ||
|
|
||
| /** | ||
| * Utility class to convert JDBC objects to columnar Arrow format objects. | ||
| * | ||
| * This utility uses following data mapping to map JDBC/SQL datatype to Arrow data types. | ||
| * | ||
| * CHAR --> ArrowType.Utf8 | ||
| * NCHAR --> ArrowType.Utf8 | ||
| * VARCHAR --> ArrowType.Utf8 | ||
| * NVARCHAR --> ArrowType.Utf8 | ||
| * LONGVARCHAR --> ArrowType.Utf8 | ||
| * LONGNVARCHAR --> ArrowType.Utf8 | ||
| * NUMERIC --> ArrowType.Decimal(precision, scale) | ||
| * DECIMAL --> ArrowType.Decimal(precision, scale) | ||
| * BIT --> ArrowType.Bool | ||
| * TINYINT --> ArrowType.Int(8, signed) | ||
| * SMALLINT --> ArrowType.Int(16, signed) | ||
| * INTEGER --> ArrowType.Int(32, signed) | ||
| * BIGINT --> ArrowType.Int(64, signed) | ||
| * REAL --> ArrowType.FloatingPoint(FloatingPointPrecision.SINGLE) | ||
| * FLOAT --> ArrowType.FloatingPoint(FloatingPointPrecision.SINGLE) | ||
| * DOUBLE --> ArrowType.FloatingPoint(FloatingPointPrecision.DOUBLE) | ||
| * BINARY --> ArrowType.Binary | ||
| * VARBINARY --> ArrowType.Binary | ||
| * LONGVARBINARY --> ArrowType.Binary | ||
| * DATE --> ArrowType.Date(DateUnit.MILLISECOND) | ||
| * TIME --> ArrowType.Time(TimeUnit.MILLISECOND, 32) | ||
| * TIMESTAMP --> ArrowType.Timestamp(TimeUnit.MILLISECOND, timezone=null) | ||
| * CLOB --> ArrowType.Utf8 | ||
| * BLOB --> ArrowType.Binary | ||
| * | ||
| * @since 0.10.0 | ||
| * @see ArrowDataFetcher | ||
| */ | ||
| public class JdbcToArrow { | ||
|
|
||
| /** | ||
| * 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 hte 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. | ||
| * @return | ||
| * @throws SQLException Propagate any SQL Exceptions to the caller after closing any resources opened such as ResultSet and Statment objects. | ||
| */ | ||
| public static VectorSchemaRoot sqlToArrow(Connection connection, String query) throws Exception { | ||
|
|
||
| assert connection != null: "JDBC conncetion object can not be null"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo in the assert message
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, will fix this.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. Also changed this to use Preconditions. |
||
| assert query != null && query.length() > 0: "SQL query can not be null or empty"; | ||
|
|
||
| RootAllocator rootAllocator = new RootAllocator(Integer.MAX_VALUE); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't the allocate be provided, so that the caller has control over it? as of now, it cannot be closed once you're done with the buffers...
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think it would be good to provide another overloaded API with RootAlocator as argument -
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If there's no way to automatically free buffers/close the allocator, you probably want to modify the existing function to take one as an input.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
|
|
||
| Statement stmt = null; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it should be possible to use
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
| ResultSet rs = null; | ||
| try { | ||
| stmt = connection.createStatement(); | ||
| rs = stmt.executeQuery(query); | ||
| ResultSetMetaData rsmd = rs.getMetaData(); | ||
| VectorSchemaRoot root = VectorSchemaRoot.create( | ||
| JdbcToArrowUtils.jdbcToArrowSchema(rsmd), rootAllocator); | ||
| JdbcToArrowUtils.jdbcToArrowVectors(rs, root); | ||
| return root; | ||
| } catch (Exception exc) { | ||
| // just throw it out after logging | ||
| throw exc; | ||
| } finally { | ||
| if (rs != null) { | ||
| rs.close(); | ||
| } | ||
| if (stmt != null) { | ||
| stmt.close(); // test | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * This method returns ArrowDataFetcher Object that can be used to fetch and iterate on the data in the given | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Object -> object (same in the argument description) |
||
| * database table. | ||
| * | ||
| * @param connection - Database connection Object | ||
| * @param tableName - Table name from which records will be fetched | ||
| * | ||
| * @return ArrowDataFetcher - Instance of ArrowDataFetcher which can be used to get Arrow Vector obejcts by calling its functionality | ||
| */ | ||
| public static ArrowDataFetcher jdbcArrowDataFetcher(Connection connection, String tableName) { | ||
| assert connection != null: "JDBC conncetion object can not be null"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same typo |
||
| assert tableName != null && tableName.length() > 0: "Table name can not be null or empty"; | ||
|
|
||
| return new ArrowDataFetcher(connection, tableName); | ||
| } | ||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't exclude these files but add a license header to them