Skip to content

Commit 690c860

Browse files
committed
feat(bigquery-jdbc): implement JDBC getObject type coercion support
1 parent e0bdb6e commit 690c860

5 files changed

Lines changed: 166 additions & 10 deletions

File tree

java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseResultSet.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,25 @@ public boolean isClosed() {
246246

247247
public abstract Object getObject(int columnIndex) throws SQLException;
248248

249+
@Override
250+
public <T> T getObject(int columnIndex, Class<T> type) throws SQLException {
251+
LOG.finestTrace("getObject");
252+
try {
253+
Object value = getObject(columnIndex);
254+
if (value == null) {
255+
return null;
256+
}
257+
return this.bigQueryTypeCoercer.coerceTo(type, value, this.LOG);
258+
} catch (BigQueryJdbcCoercionNotFoundException e) {
259+
throw createCoercionException(columnIndex, type, e);
260+
}
261+
}
262+
263+
@Override
264+
public <T> T getObject(String columnLabel, Class<T> type) throws SQLException {
265+
return getObject(getColumnIndex(columnLabel), type);
266+
}
267+
249268
protected int getColumnIndex(String columnLabel) throws SQLException {
250269
LOG.finestTrace("getColumnIndex");
251270
checkClosed();

java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryNoOpsResultSet.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -655,16 +655,6 @@ public void updateNClob(String columnLabel, Reader reader) throws SQLException {
655655
throw new BigQueryJdbcSqlFeatureNotSupportedException(METHOD_NOT_IMPLEMENTED);
656656
}
657657

658-
@Override
659-
public <T> T getObject(int columnIndex, Class<T> type) throws SQLException {
660-
throw new BigQueryJdbcSqlFeatureNotSupportedException(METHOD_NOT_IMPLEMENTED);
661-
}
662-
663-
@Override
664-
public <T> T getObject(String columnLabel, Class<T> type) throws SQLException {
665-
throw new BigQueryJdbcSqlFeatureNotSupportedException(METHOD_NOT_IMPLEMENTED);
666-
}
667-
668658
@Override
669659
public <T> T unwrap(Class<T> iface) throws SQLException {
670660
throw new BigQueryJdbcSqlFeatureNotSupportedException(METHOD_NOT_IMPLEMENTED);

java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercionUtility.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@
2929
import java.time.LocalDate;
3030
import java.time.LocalDateTime;
3131
import java.time.LocalTime;
32+
import java.time.OffsetDateTime;
3233
import java.time.Period;
34+
import java.time.ZoneOffset;
3335
import java.time.format.DateTimeFormatter;
3436
import java.time.temporal.ChronoUnit;
37+
import java.util.TimeZone;
3538
import java.util.concurrent.TimeUnit;
3639
import org.apache.arrow.vector.PeriodDuration;
3740
import org.apache.arrow.vector.util.Text;
@@ -86,6 +89,37 @@ class BigQueryTypeCoercionUtility {
8689
Timestamp.class)
8790
.registerTypeCoercion(
8891
(Date date) -> new Timestamp(date.getTime()), Date.class, Timestamp.class)
92+
.registerTypeCoercion(
93+
(LocalDateTime ldt) -> Date.valueOf(ldt.toLocalDate()),
94+
LocalDateTime.class,
95+
Date.class)
96+
.registerTypeCoercion(
97+
(LocalDateTime ldt) -> {
98+
long millisOfDay =
99+
java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(
100+
ldt.toLocalTime().toNanoOfDay());
101+
long localMillis = TimeZoneCache.getLocalMillis(millisOfDay);
102+
return new Time(localMillis);
103+
},
104+
LocalDateTime.class,
105+
Time.class)
106+
.registerTypeCoercion((Date date) -> date.toLocalDate(), Date.class, LocalDate.class)
107+
.registerTypeCoercion(
108+
(Time time) -> {
109+
long millis = time.getTime();
110+
long localMillis = millis + TimeZone.getDefault().getOffset(millis);
111+
return LocalTime.ofNanoOfDay(
112+
java.util.concurrent.TimeUnit.MILLISECONDS.toNanos(localMillis));
113+
},
114+
Time.class,
115+
LocalTime.class)
116+
.registerTypeCoercion(
117+
(Timestamp ts) -> ts.toLocalDateTime(), Timestamp.class, LocalDateTime.class)
118+
.registerTypeCoercion(
119+
(Timestamp ts) -> ts.toInstant().atOffset(ZoneOffset.UTC),
120+
Timestamp.class,
121+
OffsetDateTime.class)
122+
.registerTypeCoercion((Timestamp ts) -> ts.toInstant(), Timestamp.class, Instant.class)
89123
.registerTypeCoercion(new TimestampToString())
90124
.registerTypeCoercion(new TimeToString())
91125
.registerTypeCoercion((Long l) -> l != 0L, Long.class, Boolean.class)
@@ -106,6 +140,13 @@ class BigQueryTypeCoercionUtility {
106140
(Boolean b) -> b ? BigDecimal.ONE : BigDecimal.ZERO,
107141
Boolean.class,
108142
BigDecimal.class)
143+
.registerTypeCoercion(
144+
(Integer i) -> BigDecimal.valueOf(i), Integer.class, BigDecimal.class)
145+
.registerTypeCoercion((Long l) -> BigDecimal.valueOf(l), Long.class, BigDecimal.class)
146+
.registerTypeCoercion(
147+
(Double d) -> BigDecimal.valueOf(d), Double.class, BigDecimal.class)
148+
.registerTypeCoercion((Float f) -> BigDecimal.valueOf(f), Float.class, BigDecimal.class)
149+
.registerTypeCoercion((String s) -> new BigDecimal(s), String.class, BigDecimal.class)
109150
.registerTypeCoercion(new PeriodDurationToString())
110151
.registerTypeCoercion(unused -> (byte) 0, Void.class, Byte.class)
111152
.registerTypeCoercion(unused -> 0, Void.class, Integer.class)

java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSetTest.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static com.google.common.truth.Truth.assertThat;
2222
import static org.apache.arrow.vector.types.Types.MinorType.INT;
2323
import static org.apache.arrow.vector.types.Types.MinorType.VARCHAR;
24+
import static org.junit.jupiter.api.Assertions.assertThrows;
2425
import static org.mockito.Mockito.mock;
2526

2627
import com.google.cloud.bigquery.Field;
@@ -32,10 +33,14 @@
3233
import com.google.cloud.bigquery.storage.v1.ArrowSchema;
3334
import com.google.common.collect.ImmutableList;
3435
import java.io.IOException;
36+
import java.math.BigDecimal;
3537
import java.sql.Array;
3638
import java.sql.ResultSet;
3739
import java.sql.SQLException;
3840
import java.sql.Struct;
41+
import java.time.LocalDate;
42+
import java.time.LocalDateTime;
43+
import java.time.LocalTime;
3944
import java.util.Arrays;
4045
import java.util.List;
4146
import java.util.concurrent.BlockingQueue;
@@ -346,6 +351,53 @@ public void testIterationNested() throws SQLException {
346351
assertThat(bigQueryArrowResultSetNested.isAfterLast()).isTrue();
347352
}
348353

354+
@Test
355+
public void testGetObjectWithType() throws SQLException {
356+
assertThat(bigQueryArrowResultSet.next()).isTrue();
357+
358+
// java.time types
359+
assertThat(bigQueryArrowResultSet.getObject("dateField", LocalDate.class))
360+
.isEqualTo(LocalDate.of(1970, 1, 1));
361+
assertThat(bigQueryArrowResultSet.getObject(11, LocalDate.class))
362+
.isEqualTo(LocalDate.of(1970, 1, 1));
363+
364+
assertThat(bigQueryArrowResultSet.getObject("timeField", LocalTime.class))
365+
.isEqualTo(LocalTime.of(0, 0, 1, 234_000_000));
366+
assertThat(bigQueryArrowResultSet.getObject(10, LocalTime.class))
367+
.isEqualTo(LocalTime.of(0, 0, 1, 234_000_000));
368+
369+
assertThat(bigQueryArrowResultSet.getObject("timeStampField", LocalDateTime.class))
370+
.isEqualTo(LocalDateTime.of(1970, 1, 1, 0, 0, 0, 10_000_000));
371+
assertThat(bigQueryArrowResultSet.getObject(5, LocalDateTime.class))
372+
.isEqualTo(LocalDateTime.of(1970, 1, 1, 0, 0, 0, 10_000_000));
373+
374+
// Boolean
375+
assertThat(bigQueryArrowResultSet.getObject("boolField", Boolean.class)).isFalse();
376+
assertThat(bigQueryArrowResultSet.getObject(1, Boolean.class)).isFalse();
377+
378+
// Numbers & Coercions
379+
assertThat(bigQueryArrowResultSet.getObject("int64Filed", Long.class)).isEqualTo(1L);
380+
assertThat(bigQueryArrowResultSet.getObject(2, Integer.class)).isEqualTo(1);
381+
assertThat(bigQueryArrowResultSet.getObject(2, String.class)).isEqualTo("1");
382+
383+
assertThat(bigQueryArrowResultSet.getObject("float64Field", Double.class))
384+
.isEqualTo(Double.valueOf(1.1f));
385+
386+
// String
387+
assertThat(bigQueryArrowResultSet.getObject("stringField", String.class)).isEqualTo("text1");
388+
389+
// BigDecimal / Numeric
390+
assertThat(bigQueryArrowResultSet.getObject("numericField", BigDecimal.class))
391+
.isEqualTo(BigDecimal.ONE);
392+
assertThat(bigQueryArrowResultSet.getObject(9, Long.class)).isEqualTo(1L);
393+
394+
// Unsupported coercions should fail
395+
assertThrows(
396+
SQLException.class, () -> bigQueryArrowResultSet.getObject("boolField", LocalDate.class));
397+
assertThrows(
398+
SQLException.class, () -> bigQueryArrowResultSet.getObject("dateField", Boolean.class));
399+
}
400+
349401
private int resultSetRowCount(BigQueryArrowResultSet resultSet) throws SQLException {
350402
int rowCount = 0;
351403
while (resultSet.next()) {

java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSetTest.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static com.google.common.truth.Truth.assertThat;
2020
import static java.time.Month.MARCH;
21+
import static org.junit.jupiter.api.Assertions.assertThrows;
2122
import static org.mockito.Mockito.mock;
2223

2324
import com.google.cloud.bigquery.Field;
@@ -47,6 +48,7 @@
4748
import java.sql.Struct;
4849
import java.sql.Time;
4950
import java.sql.Timestamp;
51+
import java.time.LocalDate;
5052
import java.time.LocalDateTime;
5153
import java.time.LocalTime;
5254
import java.util.Calendar;
@@ -466,6 +468,58 @@ public void testDate() throws SQLException {
466468
.isEqualTo(bigQueryJsonResultSet.getDate("fourteenth", calendar).getTime());
467469
}
468470

471+
@Test
472+
public void testGetObjectWithType() throws SQLException {
473+
assertThat(resetResultSet()).isTrue();
474+
475+
// java.time types
476+
assertThat(bigQueryJsonResultSet.getObject("fourteenth", LocalDate.class))
477+
.isEqualTo(LocalDate.of(2020, 1, 15));
478+
assertThat(bigQueryJsonResultSet.getObject(14, LocalDate.class))
479+
.isEqualTo(LocalDate.of(2020, 1, 15));
480+
481+
assertThat(bigQueryJsonResultSet.getObject("twelfth", LocalTime.class))
482+
.isEqualTo(LocalTime.of(11, 14, 19, 820000000));
483+
assertThat(bigQueryJsonResultSet.getObject(12, LocalTime.class))
484+
.isEqualTo(LocalTime.of(11, 14, 19, 820000000));
485+
486+
assertThat(bigQueryJsonResultSet.getObject("fifth", LocalDateTime.class))
487+
.isEqualTo(LocalDateTime.of(2023, 3, 30, 11, 14, 19, 820000000));
488+
assertThat(bigQueryJsonResultSet.getObject(5, LocalDateTime.class))
489+
.isEqualTo(LocalDateTime.of(2023, 3, 30, 11, 14, 19, 820000000));
490+
491+
// Boolean
492+
assertThat(bigQueryJsonResultSet.getObject("first", Boolean.class)).isFalse();
493+
assertThat(bigQueryJsonResultSet.getObject(1, Boolean.class)).isFalse();
494+
495+
// Numbers & Coercions
496+
assertThat(bigQueryJsonResultSet.getObject("second", Long.class)).isEqualTo(1L);
497+
assertThat(bigQueryJsonResultSet.getObject(2, Integer.class)).isEqualTo(1);
498+
assertThat(bigQueryJsonResultSet.getObject(2, Short.class)).isEqualTo((short) 1);
499+
assertThat(bigQueryJsonResultSet.getObject(2, String.class)).isEqualTo("1");
500+
501+
assertThat(bigQueryJsonResultSet.getObject("third", Double.class)).isEqualTo(1.5D);
502+
assertThat(bigQueryJsonResultSet.getObject(3, Float.class)).isEqualTo(1.5F);
503+
504+
// String
505+
assertThat(bigQueryJsonResultSet.getObject("fourth", String.class)).isEqualTo(STRING_VAL);
506+
507+
// BigDecimal / Numeric
508+
assertThat(bigQueryJsonResultSet.getObject("tenth", BigDecimal.class))
509+
.isEqualTo(new BigDecimal("12345678"));
510+
assertThat(bigQueryJsonResultSet.getObject(10, Long.class)).isEqualTo(12345678L);
511+
512+
assertThat(bigQueryJsonResultSet.getObject("eleventh", BigDecimal.class))
513+
.isEqualTo(new BigDecimal("12345678.99"));
514+
assertThat(bigQueryJsonResultSet.getObject(11, Double.class)).isEqualTo(12345678.99D);
515+
516+
// Unsupported coercions should fail
517+
assertThrows(
518+
SQLException.class, () -> bigQueryJsonResultSet.getObject("first", LocalDate.class));
519+
assertThrows(
520+
SQLException.class, () -> bigQueryJsonResultSet.getObject("fourteenth", Boolean.class));
521+
}
522+
469523
private int resultSetRowCount(BigQueryJsonResultSet resultSet) throws SQLException {
470524
int rowCount = 0;
471525
while (resultSet.next()) {

0 commit comments

Comments
 (0)