Skip to content

Commit c598ad0

Browse files
authored
Merge branch 'main' into fix-grpc
2 parents 61dc92f + 72d4ab6 commit c598ad0

7 files changed

Lines changed: 231 additions & 46 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 (RuntimeException 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: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
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;
3537
import java.util.concurrent.TimeUnit;
@@ -65,27 +67,68 @@ class BigQueryTypeCoercionUtility {
6567
.registerTypeCoercion(new BytesArrayToString())
6668

6769
// Read API Type coercions
68-
.registerTypeCoercion(Timestamp::valueOf, LocalDateTime.class, Timestamp.class)
70+
.registerTypeCoercion(
71+
(LocalDateTime ldt) -> Timestamp.from(ldt.toInstant(ZoneOffset.UTC)),
72+
LocalDateTime.class,
73+
Timestamp.class)
6974
.registerTypeCoercion(Text::toString, Text.class, String.class)
7075
.registerTypeCoercion(new TextToInteger())
7176
.registerTypeCoercion(new LongToTimestamp())
7277
.registerTypeCoercion(new LongToTime())
7378
.registerTypeCoercion(new IntegerToDate())
7479
.registerTypeCoercion(
75-
(Timestamp ts) -> Date.valueOf(ts.toLocalDateTime().toLocalDate()),
80+
(Timestamp ts) ->
81+
Date.valueOf(ts.toInstant().atOffset(ZoneOffset.UTC).toLocalDate()),
7682
Timestamp.class,
7783
Date.class)
7884
.registerTypeCoercion(
79-
(Timestamp ts) -> Time.valueOf(ts.toLocalDateTime().toLocalTime()),
85+
(Timestamp ts) ->
86+
Time.valueOf(ts.toInstant().atOffset(ZoneOffset.UTC).toLocalTime()),
8087
Timestamp.class,
8188
Time.class)
8289
.registerTypeCoercion(
8390
(Time time) -> // Per JDBC spec, the date component should be 1970-01-01
84-
Timestamp.valueOf(LocalDateTime.of(LocalDate.ofEpochDay(0), time.toLocalTime())),
91+
Timestamp.from(
92+
LocalDateTime.of(LocalDate.ofEpochDay(0), time.toLocalTime())
93+
.toInstant(ZoneOffset.UTC)),
8594
Time.class,
8695
Timestamp.class)
8796
.registerTypeCoercion(
8897
(Date date) -> new Timestamp(date.getTime()), Date.class, Timestamp.class)
98+
.registerTypeCoercion(
99+
(LocalDateTime ldt) -> Date.valueOf(ldt.toLocalDate()),
100+
LocalDateTime.class,
101+
Date.class)
102+
.registerTypeCoercion(
103+
(LocalDateTime ldt) -> {
104+
// Custom conversion is used to preserve sub-second (millisecond) precision,
105+
// as standard java.sql.Time.valueOf(LocalTime) truncates milliseconds.
106+
long millisOfDay = TimeUnit.NANOSECONDS.toMillis(ldt.toLocalTime().toNanoOfDay());
107+
long localMillis = TimeZoneCache.getLocalMillis(millisOfDay);
108+
return new Time(localMillis);
109+
},
110+
LocalDateTime.class,
111+
Time.class)
112+
.registerTypeCoercion((Date date) -> date.toLocalDate(), Date.class, LocalDate.class)
113+
.registerTypeCoercion(
114+
(Time time) -> {
115+
// Custom conversion is used to preserve sub-second (millisecond) precision,
116+
// as standard java.sql.Time.toLocalTime() truncates milliseconds.
117+
long millis = time.getTime();
118+
long localMillis = millis + TimeZoneCache.getOffset(millis);
119+
return LocalTime.ofNanoOfDay(TimeUnit.MILLISECONDS.toNanos(localMillis));
120+
},
121+
Time.class,
122+
LocalTime.class)
123+
.registerTypeCoercion(
124+
(Timestamp ts) -> ts.toInstant().atOffset(ZoneOffset.UTC).toLocalDateTime(),
125+
Timestamp.class,
126+
LocalDateTime.class)
127+
.registerTypeCoercion(
128+
(Timestamp ts) -> ts.toInstant().atOffset(ZoneOffset.UTC),
129+
Timestamp.class,
130+
OffsetDateTime.class)
131+
.registerTypeCoercion((Timestamp ts) -> ts.toInstant(), Timestamp.class, Instant.class)
89132
.registerTypeCoercion(new TimestampToString())
90133
.registerTypeCoercion(new TimeToString())
91134
.registerTypeCoercion((Long l) -> l != 0L, Long.class, Boolean.class)
@@ -106,6 +149,13 @@ class BigQueryTypeCoercionUtility {
106149
(Boolean b) -> b ? BigDecimal.ONE : BigDecimal.ZERO,
107150
Boolean.class,
108151
BigDecimal.class)
152+
.registerTypeCoercion(
153+
(Integer i) -> BigDecimal.valueOf(i), Integer.class, BigDecimal.class)
154+
.registerTypeCoercion((Long l) -> BigDecimal.valueOf(l), Long.class, BigDecimal.class)
155+
.registerTypeCoercion(
156+
(Double d) -> BigDecimal.valueOf(d), Double.class, BigDecimal.class)
157+
.registerTypeCoercion((Float f) -> BigDecimal.valueOf(f), Float.class, BigDecimal.class)
158+
.registerTypeCoercion((String s) -> new BigDecimal(s), String.class, BigDecimal.class)
109159
.registerTypeCoercion(new PeriodDurationToString())
110160
.registerTypeCoercion(unused -> (byte) 0, Void.class, Byte.class)
111161
.registerTypeCoercion(unused -> 0, Void.class, Integer.class)

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,8 @@ public static void reset() {
3030
public static long getLocalMillis(long millisOfDay) {
3131
return millisOfDay - defaultTimeZone.getOffset(millisOfDay);
3232
}
33+
34+
public static int getOffset(long millis) {
35+
return defaultTimeZone.getOffset(millis);
36+
}
3337
}

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

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,36 @@
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;
2728
import com.google.cloud.bigquery.Field.Mode;
2829
import com.google.cloud.bigquery.FieldList;
2930
import com.google.cloud.bigquery.Schema;
3031
import com.google.cloud.bigquery.StandardSQLTypeName;
32+
import com.google.cloud.bigquery.jdbc.rules.TimeZoneRule;
3133
import com.google.cloud.bigquery.storage.v1.ArrowRecordBatch;
3234
import com.google.cloud.bigquery.storage.v1.ArrowSchema;
3335
import com.google.common.collect.ImmutableList;
3436
import java.io.IOException;
37+
import java.math.BigDecimal;
3538
import java.sql.Array;
39+
import java.sql.Date;
3640
import java.sql.ResultSet;
3741
import java.sql.SQLException;
3842
import java.sql.Struct;
43+
import java.sql.Time;
44+
import java.sql.Timestamp;
45+
import java.time.LocalDate;
46+
import java.time.LocalDateTime;
47+
import java.time.LocalTime;
3948
import java.util.Arrays;
4049
import java.util.List;
50+
import java.util.TimeZone;
4151
import java.util.concurrent.BlockingQueue;
4252
import java.util.concurrent.LinkedBlockingDeque;
53+
import java.util.stream.Stream;
4354
import org.apache.arrow.memory.RootAllocator;
4455
import org.apache.arrow.vector.BitVector;
4556
import org.apache.arrow.vector.DateMilliVector;
@@ -59,9 +70,20 @@
5970
import org.apache.arrow.vector.util.Text;
6071
import org.junit.jupiter.api.BeforeEach;
6172
import org.junit.jupiter.api.Test;
73+
import org.junit.jupiter.api.extension.RegisterExtension;
74+
import org.junit.jupiter.params.ParameterizedTest;
75+
import org.junit.jupiter.params.provider.Arguments;
76+
import org.junit.jupiter.params.provider.MethodSource;
6277

6378
public class BigQueryArrowResultSetTest {
6479

80+
static {
81+
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
82+
TimeZoneCache.reset();
83+
}
84+
85+
@RegisterExtension public static final TimeZoneRule timeZoneRule = new TimeZoneRule("UTC");
86+
6587
private static final FieldList fieldList =
6688
FieldList.of(
6789
Field.of("boolField", StandardSQLTypeName.BOOL),
@@ -110,7 +132,7 @@ private VectorSchemaRoot getTestVectorSchemaRoot() {
110132
Float8Vector float64Field =
111133
new Float8Vector("float64Field", allocator); // Mapped with StandardSQLTypeName.FLOAT64
112134
float64Field.allocateNew(2);
113-
float64Field.set(0, 1.1f);
135+
float64Field.set(0, 1.1);
114136
float64Field.setValueCount(1);
115137
VarCharVector stringField =
116138
new VarCharVector("stringField", allocator); // Mapped with StandardSQLTypeName.STRING
@@ -346,6 +368,66 @@ public void testIterationNested() throws SQLException {
346368
assertThat(bigQueryArrowResultSetNested.isAfterLast()).isTrue();
347369
}
348370

371+
public static Stream<Arguments> successfulCoercionCases() {
372+
return Stream.of(
373+
Arguments.of("dateField", LocalDate.class, LocalDate.of(1970, 1, 1)),
374+
Arguments.of(11, LocalDate.class, LocalDate.of(1970, 1, 1)),
375+
Arguments.of("timeField", LocalTime.class, LocalTime.of(0, 0, 1, 234_000_000)),
376+
Arguments.of(10, LocalTime.class, LocalTime.of(0, 0, 1, 234_000_000)),
377+
Arguments.of(
378+
"timeStampField",
379+
LocalDateTime.class,
380+
LocalDateTime.of(1970, 1, 1, 0, 0, 0, 10_000_000)),
381+
Arguments.of(5, LocalDateTime.class, LocalDateTime.of(1970, 1, 1, 0, 0, 0, 10_000_000)),
382+
Arguments.of("boolField", Boolean.class, false),
383+
Arguments.of(1, Boolean.class, false),
384+
Arguments.of("int64Filed", Long.class, 1L),
385+
Arguments.of(2, Integer.class, 1),
386+
Arguments.of(2, String.class, "1"),
387+
Arguments.of("float64Field", Double.class, 1.1),
388+
Arguments.of("stringField", String.class, "text1"),
389+
Arguments.of("numericField", BigDecimal.class, BigDecimal.ONE),
390+
Arguments.of(9, Long.class, 1L),
391+
Arguments.of("timeField", Time.class, new Time(1234L)),
392+
Arguments.of(10, Time.class, new Time(1234L)),
393+
Arguments.of("timeStampField", Timestamp.class, new Timestamp(10L)),
394+
Arguments.of(5, Timestamp.class, new Timestamp(10L)),
395+
Arguments.of("dateField", Date.class, new Date(0L)),
396+
Arguments.of(11, Date.class, new Date(0L)));
397+
}
398+
399+
public static Stream<Arguments> failingCoercionCases() {
400+
return Stream.of(
401+
Arguments.of("boolField", LocalDate.class),
402+
Arguments.of("dateField", Boolean.class),
403+
Arguments.of("stringField", BigDecimal.class));
404+
}
405+
406+
@ParameterizedTest
407+
@MethodSource("successfulCoercionCases")
408+
public void testGetObjectWithType_success(Object column, Class<?> type, Object expectedValue)
409+
throws SQLException {
410+
assertThat(bigQueryArrowResultSet.next()).isTrue();
411+
if (column instanceof String) {
412+
assertThat(bigQueryArrowResultSet.getObject((String) column, type)).isEqualTo(expectedValue);
413+
} else {
414+
assertThat(bigQueryArrowResultSet.getObject((Integer) column, type)).isEqualTo(expectedValue);
415+
}
416+
}
417+
418+
@ParameterizedTest
419+
@MethodSource("failingCoercionCases")
420+
public void testGetObjectWithType_failure(Object column, Class<?> type) throws SQLException {
421+
assertThat(bigQueryArrowResultSet.next()).isTrue();
422+
if (column instanceof String) {
423+
assertThrows(
424+
SQLException.class, () -> bigQueryArrowResultSet.getObject((String) column, type));
425+
} else {
426+
assertThrows(
427+
SQLException.class, () -> bigQueryArrowResultSet.getObject((Integer) column, type));
428+
}
429+
}
430+
349431
private int resultSetRowCount(BigQueryArrowResultSet resultSet) throws SQLException {
350432
int rowCount = 0;
351433
while (resultSet.next()) {

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@
1616

1717
package com.google.cloud.bigquery.jdbc;
1818

19-
import static org.junit.jupiter.api.Assertions.*;
19+
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
import static org.junit.jupiter.api.Assertions.assertFalse;
21+
import static org.junit.jupiter.api.Assertions.assertNotNull;
22+
import static org.junit.jupiter.api.Assertions.assertNull;
23+
import static org.junit.jupiter.api.Assertions.assertTrue;
2024

2125
import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider;
2226
import com.google.api.gax.rpc.HeaderProvider;

0 commit comments

Comments
 (0)