diff --git a/pom.xml b/pom.xml
index dc202f93..f61e8767 100644
--- a/pom.xml
+++ b/pom.xml
@@ -230,6 +230,7 @@
1.7
+
diff --git a/src/main/java/net/starschema/clouddb/jdbc/DateTimeUtils.java b/src/main/java/net/starschema/clouddb/jdbc/DateTimeUtils.java
index e54a65d7..be123d1c 100644
--- a/src/main/java/net/starschema/clouddb/jdbc/DateTimeUtils.java
+++ b/src/main/java/net/starschema/clouddb/jdbc/DateTimeUtils.java
@@ -99,6 +99,9 @@ static Timestamp parseDateTime(String value, Calendar cal) throws SQLException {
/** Parse a BigQuery TIMESTAMP literal, represented as the number of seconds since epoch. */
static Timestamp parseTimestamp(String value) throws SQLException {
+ if (value == null) {
+ return null;
+ }
try {
// BigQuery TIMESTAMP has a string representation that looks like e.g. "1.288061375E9"
// for 2010-10-26 02:49:35 UTC.
@@ -119,6 +122,9 @@ static Timestamp parseTimestamp(String value) throws SQLException {
}
static String formatTimestamp(String rawString) throws SQLException {
+ if (rawString == null) {
+ return null;
+ }
Timestamp timestamp = parseTimestamp(rawString);
return DATETIME_FORMATTER.format(OffsetDateTime.ofInstant(timestamp.toInstant(), UTC_ZONE))
+ " UTC";
diff --git a/src/test/java/net/starschema/clouddb/jdbc/BQForwardOnlyResultSetFunctionTest.java b/src/test/java/net/starschema/clouddb/jdbc/BQForwardOnlyResultSetFunctionTest.java
index 7c1b4a3f..a1e5a425 100644
--- a/src/test/java/net/starschema/clouddb/jdbc/BQForwardOnlyResultSetFunctionTest.java
+++ b/src/test/java/net/starschema/clouddb/jdbc/BQForwardOnlyResultSetFunctionTest.java
@@ -38,6 +38,7 @@
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
+import java.time.Instant;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
@@ -796,6 +797,62 @@ public void testHandlesAllNullResponseFields() throws Exception {
throw new AssertionError("Expected graceful failure due to lack of job reference");
}
+ @Test
+ public void testHandlesNullTimeDateObjects() throws Exception {
+ this.NewConnection("&useLegacySql=false");
+ Statement stmt =
+ BQForwardOnlyResultSetFunctionTest.con.createStatement(
+ ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
+
+ final String date = "2011-11-11";
+ final String time = "12:12:12";
+ final String dateTime = date + " " + time;
+ final String dateTimeWithT = date + "T" + time;
+ // The number of milliseconds between epoch and 2011-11-11 12:12:12 UTC+0.
+ final long millis = 1321013532000L;
+
+ // spotless:off
+ String sql = "SELECT " +
+ "TIMESTAMP('" + dateTime + "') AS ts, " +
+ "DATETIME('" + dateTime + "') AS dt, " +
+ "DATE('" + date + "') AS d, " +
+ "TIME(12, 12, 12) AS t\n" +
+ "UNION ALL SELECT " +
+ "CASE WHEN 1 = 0 THEN TIMESTAMP('" + dateTime + "') ELSE NULL END, " +
+ "CASE WHEN 1 = 0 THEN DATETIME('" + dateTime + "') ELSE NULL END, " +
+ "CASE WHEN 1 = 0 THEN DATE('" + date + "') ELSE NULL END, " +
+ "CASE WHEN 1 = 0 THEN TIME(12, 12, 12) ELSE NULL END";
+ // spotless:on
+
+ ResultSet results = stmt.executeQuery(sql);
+
+ // First row has all non-null objects.
+ Assertions.assertThat(results.next()).isTrue();
+ Assertions.assertThat(results.getObject("ts"))
+ .isEqualTo(Timestamp.from(Instant.ofEpochMilli(millis)));
+ Assertions.assertThat(results.getString("ts")).isEqualTo(dateTime + " UTC");
+ Assertions.assertThat(results.getObject("dt")).isEqualTo(Timestamp.valueOf(dateTime));
+ Assertions.assertThat(results.getString("dt")).isEqualTo(dateTimeWithT);
+ Assertions.assertThat(results.getObject("d")).isEqualTo(java.sql.Date.valueOf(date));
+ Assertions.assertThat(results.getString("d")).isEqualTo(date);
+ Assertions.assertThat(results.getObject("t")).isEqualTo(java.sql.Time.valueOf(time));
+ Assertions.assertThat(results.getString("t")).isEqualTo(time);
+
+ // Second row is all null.
+ Assertions.assertThat(results.next()).isTrue();
+ Assertions.assertThat(results.getObject("ts")).isNull();
+ Assertions.assertThat(results.getString("ts")).isNull();
+ Assertions.assertThat(results.getObject("dt")).isNull();
+ Assertions.assertThat(results.getString("dt")).isNull();
+ Assertions.assertThat(results.getObject("d")).isNull();
+ Assertions.assertThat(results.getString("d")).isNull();
+ Assertions.assertThat(results.getObject("t")).isNull();
+ Assertions.assertThat(results.getString("t")).isNull();
+
+ // Only two rows.
+ Assertions.assertThat(results.next()).isFalse();
+ }
+
@Test
public void testHandlesSomeNullResponseFields() throws Exception {
// Make sure we don't get any NPE's due to null values;