Repository navigation
[Java][JDBC] Converting TIMESTAMP_WITH_TIMEZONE #37021
Description
Describe the usage question you have. Please include as many useful details as possible.
Error description
We have a Java FlightServer for Arrow Flight SQL that connects to various types of datawarehouses through JDBC and converts the results into arrow to send back using sqlToArrowVectorIterator. Everything has been working pretty well, but we just ran into some issues with TIMESTAMP_TZ columns on Snowflake.
We were getting the following exception (redacted):
java.lang.RuntimeException: Error occurred while creating iterator.
at org.apache.arrow.adapter.jdbc.ArrowVectorIterator.create(ArrowVectorIterator.java:87)
at org.apache.arrow.adapter.jdbc.JdbcToArrow.sqlToArrowVectorIterator(JdbcToArrow.java:101)
...
Caused by: java.lang.NullPointerException: Cannot invoke "org.apache.arrow.vector.types.pojo.ArrowType.getTypeID()" because "arrowType" is null
at org.apache.arrow.adapter.jdbc.JdbcToArrowUtils.getConsumer(JdbcToArrowUtils.java:437)
at org.apache.arrow.adapter.jdbc.ArrowVectorIterator.initialize(ArrowVectorIterator.java:142)
at org.apache.arrow.adapter.jdbc.ArrowVectorIterator.createVectorSchemaRoot(ArrowVectorIterator.java:134)
After digging around, I noticed we were getting back a JDBC type TIMESTAMP_WITH_TIMEZONE (2014) which wasn't mapped by the default converter JdbcToArrowUtils.getArrowTypeFromJdbcType. I created my own converter that mapped that type the same way it maps TIMESTAMP to get me unblocked (figured I'd deal with the TZ issue later). Now I don't get the exception, but the TIMESTAMP_WITH_TIMEZONE is excluded from the results. For example here's how I set up the data in Snowflake:
And here's what I get when I query through Dbeaver using the AFS JDBC driver through our service:
Debugging attempts
My attempts at debugging this have yielded very strange results:
- When I look in the debugger on our service, the
VectorSchemaRootlooks correct:
- If I try to select just the NTZ column it works fine:
- If I try to select just the TZ column it seems to try to make a
acceptPutPreparedStatementUpdatecall, which we haven't implemented so it fails.
My guess is that this is a bug on the JDBC driver, but as far as that's concerned the 2 vectors are of the same type, so I don't see why it would just drop one column.
Any help would be greatly appreciated!
Component(s)
Java
Activity
aiguofer commentedon Aug 4, 2023
Nvm, I figured it out. The issue is we were still using
jdbcToArrowSchema(ResultSetMetaData rsmd, Calendar calendar), which uses the default converter in thecreatePreparedStatementrequest. It seems that whenjdbcToArrowSchemacan't map a specific type, it simply excludes thatFieldin theSchemathat it generates. Then, when you dogetStreamPreparedStatementthe conversion fails.By passing the same custom
JdbcToArrowConfigwith our custom converter to bothjdbcToArrowSchemaand tosqlToArrowVectorIteratorwe're now getting the right results!! Now I just have to make sure we set the TZ correctly, which might require querying snowflake for the current session timezone... this might be tough, especially if a user changes the session timezone while talking to our service -.-Would it make sense to make a bug report for the behavior discrepancy between
jdbcToArrowSchemaandsqlToArrowVectorIteratorwith regards to unmapped types? or to add support forTIMESTAMP_WITH_TIMEZONEin the default converter?lidavidm commentedon Aug 4, 2023
Yes, we should error instead of silently dropping a field; and we should support TIMESTAMP WITH TIMEZONE if possible, though I wonder if the semantics are consistent enough between vendors to allow this. (I ran into this with ADBC: you can't add your own converters, so I was considering forking arrow-jdbc for that...)
aiguofer commentedon Aug 4, 2023
Cool that makes sense!
I've been digging more into this and I'm a bit confused. It seems like the default is to set the timezone associated with the
config.calendarto regularTIMESTAMPw/o TZ.Shouldn't the TZ be
nullforTIMESTAMPand then set the TZ based on theconfig.calendarlike this (in Kotlin):I tried doing this but then I got an error in
JdbcToArrowUtils.getConsumerbecause of:it seems to me like that should be
It's a little weird because you can specify the converter but not the consumer, but the consumer seems to be coupled to the default converter.
I'd be happy to start a PR for either:
getConsumerpluggable like the converterTIMESTAMP_WITH_TIMEZONElidavidm commentedon Aug 7, 2023
#36519 might be related? The timestamp handling is iffy in general.
Any/all of those 3 would be welcome.
aiguofer commentedon Aug 9, 2023
Hmmm that's interesting! I think it's related, although that's about handling timestamps client-side on the driver. The issue here is in the conversion from a JDBC ResultSet. I'll put up a PR with what I think should be the correct behavior, but this might affect the other conversation since it affects what is returned to the driver if using this conversion on the server side.
GH-37021 [Java][arrow-jdbc] Pluggable getConsumer (#37085)
lidavidm commentedon Aug 10, 2023
Ah - do you mind opening a separate issue for the other PR?
apacheGH-37021 [Java][arrow-jdbc] Pluggable getConsumer (apache#37085)
GH-37021 [Java][arrow-jdbc] Pluggable getConsumer (#37085)