-
Notifications
You must be signed in to change notification settings - Fork 1.2k
fix(bigquery-jdbc): fix time getters #13868
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
base: main
Are you sure you want to change the base?
Changes from all commits
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 |
|---|---|---|
|
|
@@ -116,6 +116,10 @@ <T> T coerceTo(Class<T> targetClass, Object value, BigQueryJdbcResultSetLogger l | |
| return null; | ||
| } | ||
| if (coercion == null) { | ||
| if (targetClass.isAssignableFrom(sourceClass) | ||
| || (sourceClass.isArray() && targetClass.isArray())) { | ||
| return (T) value; | ||
| } | ||
|
Comment on lines
+119
to
+122
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. The condition if (targetClass.isAssignableFrom(sourceClass)) {
return (T) value;
} |
||
| if (targetClass.equals(String.class)) { | ||
| return (T) value.toString(); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -138,6 +138,27 @@ static Timestamp convertTimestampWithCalendar(Timestamp timestamp, Calendar cal) | |||||||||||||||||||||||||||||||||||||||||||||
| return adjustedTimestamp; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| static Time localTimeToTime(LocalTime lt) { | ||||||||||||||||||||||||||||||||||||||||||||||
| long epochMillis = | ||||||||||||||||||||||||||||||||||||||||||||||
| lt.atDate(LocalDate.of(1970, 1, 1)) | ||||||||||||||||||||||||||||||||||||||||||||||
| .atZone(ZoneId.systemDefault()) | ||||||||||||||||||||||||||||||||||||||||||||||
| .toInstant() | ||||||||||||||||||||||||||||||||||||||||||||||
| .toEpochMilli(); | ||||||||||||||||||||||||||||||||||||||||||||||
| return new Time(epochMillis); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+141
to
+148
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. According to the project's general rules, when converting or shifting timezone fields for JDBC types like static Time localTimeToTime(LocalTime lt) {
Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(1970, Calendar.JANUARY, 1, lt.getHour(), lt.getMinute(), lt.getSecond());
cal.set(Calendar.MILLISECOND, lt.getNano() / 1_000_000);
return new Time(cal.getTimeInMillis());
}References
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| static LocalDateTime parseFieldValueToLocalDateTime(FieldValue fv) { | ||||||||||||||||||||||||||||||||||||||||||||||
| String raw = fv.getStringValue(); | ||||||||||||||||||||||||||||||||||||||||||||||
| if (raw.contains("T") || raw.contains(" ")) { | ||||||||||||||||||||||||||||||||||||||||||||||
| return LocalDateTime.parse(raw.replace(' ', 'T')); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| long micros = fv.getTimestampValue(); | ||||||||||||||||||||||||||||||||||||||||||||||
| return Instant.EPOCH | ||||||||||||||||||||||||||||||||||||||||||||||
| .plus(micros, ChronoUnit.MICROS) | ||||||||||||||||||||||||||||||||||||||||||||||
| .atOffset(ZoneOffset.UTC) | ||||||||||||||||||||||||||||||||||||||||||||||
| .toLocalDateTime(); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+150
to
+160
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. The
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| static BigQueryTypeCoercer INSTANCE; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| static { | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -163,7 +184,7 @@ static Timestamp convertTimestampWithCalendar(Timestamp timestamp, Calendar cal) | |||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Read API Type coercions | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (LocalDateTime ldt) -> Timestamp.from(ldt.toInstant(ZoneOffset.UTC)), | ||||||||||||||||||||||||||||||||||||||||||||||
| (LocalDateTime ldt) -> Timestamp.valueOf(ldt), | ||||||||||||||||||||||||||||||||||||||||||||||
| LocalDateTime.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| Timestamp.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion(Text::toString, Text.class, String.class) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -172,58 +193,110 @@ static Timestamp convertTimestampWithCalendar(Timestamp timestamp, Calendar cal) | |||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion(new LongToTime()) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion(new IntegerToDate()) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (Timestamp ts) -> | ||||||||||||||||||||||||||||||||||||||||||||||
| Date.valueOf(ts.toInstant().atOffset(ZoneOffset.UTC).toLocalDate()), | ||||||||||||||||||||||||||||||||||||||||||||||
| (Timestamp ts) -> Date.valueOf(ts.toLocalDateTime().toLocalDate()), | ||||||||||||||||||||||||||||||||||||||||||||||
| Timestamp.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| Date.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (Timestamp ts) -> | ||||||||||||||||||||||||||||||||||||||||||||||
| Time.valueOf(ts.toInstant().atOffset(ZoneOffset.UTC).toLocalTime()), | ||||||||||||||||||||||||||||||||||||||||||||||
| (Timestamp ts) -> localTimeToTime(ts.toLocalDateTime().toLocalTime()), | ||||||||||||||||||||||||||||||||||||||||||||||
| Timestamp.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| Time.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (Time time) -> // Per JDBC spec, the date component should be 1970-01-01 | ||||||||||||||||||||||||||||||||||||||||||||||
| Timestamp.from( | ||||||||||||||||||||||||||||||||||||||||||||||
| LocalDateTime.of(LocalDate.ofEpochDay(0), time.toLocalTime()) | ||||||||||||||||||||||||||||||||||||||||||||||
| .toInstant(ZoneOffset.UTC)), | ||||||||||||||||||||||||||||||||||||||||||||||
| Time.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| Timestamp.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| (Time time) -> new Timestamp(time.getTime()), Time.class, Timestamp.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (Date date) -> new Timestamp(date.getTime()), Date.class, Timestamp.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (LocalDateTime ldt) -> Date.valueOf(ldt.toLocalDate()), | ||||||||||||||||||||||||||||||||||||||||||||||
| LocalDateTime.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| Date.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (LocalDateTime ldt) -> { | ||||||||||||||||||||||||||||||||||||||||||||||
| // Custom conversion is used to preserve sub-second (millisecond) precision, | ||||||||||||||||||||||||||||||||||||||||||||||
| // as standard java.sql.Time.valueOf(LocalTime) truncates milliseconds. | ||||||||||||||||||||||||||||||||||||||||||||||
| long millisOfDay = TimeUnit.NANOSECONDS.toMillis(ldt.toLocalTime().toNanoOfDay()); | ||||||||||||||||||||||||||||||||||||||||||||||
| long localMillis = TimeZoneCache.getLocalMillis(millisOfDay); | ||||||||||||||||||||||||||||||||||||||||||||||
| return new Time(localMillis); | ||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||
| (LocalDateTime ldt) -> localTimeToTime(ldt.toLocalTime()), | ||||||||||||||||||||||||||||||||||||||||||||||
| LocalDateTime.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| Time.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion((Date date) -> date.toLocalDate(), Date.class, LocalDate.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (Time time) -> { | ||||||||||||||||||||||||||||||||||||||||||||||
| // Custom conversion is used to preserve sub-second (millisecond) precision, | ||||||||||||||||||||||||||||||||||||||||||||||
| // as standard java.sql.Time.toLocalTime() truncates milliseconds. | ||||||||||||||||||||||||||||||||||||||||||||||
| long millis = time.getTime(); | ||||||||||||||||||||||||||||||||||||||||||||||
| long localMillis = millis + TimeZoneCache.getOffset(millis); | ||||||||||||||||||||||||||||||||||||||||||||||
| return LocalTime.ofNanoOfDay(TimeUnit.MILLISECONDS.toNanos(localMillis)); | ||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||
| (Date date) -> date.toLocalDate().atStartOfDay(), | ||||||||||||||||||||||||||||||||||||||||||||||
| Date.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| LocalDateTime.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (Time time) -> | ||||||||||||||||||||||||||||||||||||||||||||||
| Instant.ofEpochMilli(time.getTime()) | ||||||||||||||||||||||||||||||||||||||||||||||
| .atZone(ZoneId.systemDefault()) | ||||||||||||||||||||||||||||||||||||||||||||||
| .toLocalTime(), | ||||||||||||||||||||||||||||||||||||||||||||||
| Time.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| LocalTime.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (Timestamp ts) -> ts.toInstant().atOffset(ZoneOffset.UTC).toLocalDateTime(), | ||||||||||||||||||||||||||||||||||||||||||||||
| (Timestamp ts) -> ts.toLocalDateTime(), Timestamp.class, LocalDateTime.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (Timestamp ts) -> ts.toLocalDateTime().toLocalDate(), | ||||||||||||||||||||||||||||||||||||||||||||||
| Timestamp.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| LocalDateTime.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| LocalDate.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (Timestamp ts) -> ts.toInstant().atOffset(ZoneOffset.UTC), | ||||||||||||||||||||||||||||||||||||||||||||||
| (Timestamp ts) -> ts.toLocalDateTime().toLocalTime(), | ||||||||||||||||||||||||||||||||||||||||||||||
| Timestamp.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| LocalTime.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (Timestamp ts) -> ts.toLocalDateTime().atOffset(ZoneOffset.UTC), | ||||||||||||||||||||||||||||||||||||||||||||||
| Timestamp.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| OffsetDateTime.class) | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+237
to
240
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. Converting
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion((Timestamp ts) -> ts.toInstant(), Timestamp.class, Instant.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (Timestamp ts) -> ts.toLocalDateTime().atZone(ZoneOffset.UTC), | ||||||||||||||||||||||||||||||||||||||||||||||
| Timestamp.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| ZonedDateTime.class) | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+241
to
+244
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. Converting
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (Timestamp ts) -> ts.toLocalDateTime().atOffset(ZoneOffset.UTC).toInstant(), | ||||||||||||||||||||||||||||||||||||||||||||||
| Timestamp.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| Instant.class) | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+245
to
+248
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. Converting
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (LocalDateTime ldt) -> ldt.toLocalDate(), LocalDateTime.class, LocalDate.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (LocalDateTime ldt) -> ldt.toLocalTime(), LocalDateTime.class, LocalTime.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (LocalDateTime ldt) -> ldt.atOffset(ZoneOffset.UTC), | ||||||||||||||||||||||||||||||||||||||||||||||
| LocalDateTime.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| OffsetDateTime.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (LocalDateTime ldt) -> ldt.atZone(ZoneOffset.UTC), | ||||||||||||||||||||||||||||||||||||||||||||||
| LocalDateTime.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| ZonedDateTime.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (LocalDateTime ldt) -> ldt.toInstant(ZoneOffset.UTC), | ||||||||||||||||||||||||||||||||||||||||||||||
| LocalDateTime.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| Instant.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion((LocalDate ld) -> Date.valueOf(ld), LocalDate.class, Date.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (LocalDate ld) -> Timestamp.valueOf(ld.atStartOfDay()), | ||||||||||||||||||||||||||||||||||||||||||||||
| LocalDate.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| Timestamp.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (LocalDate ld) -> ld.atStartOfDay(), LocalDate.class, LocalDateTime.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| BigQueryTypeCoercionUtility::localTimeToTime, | ||||||||||||||||||||||||||||||||||||||||||||||
| LocalTime.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| Time.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (FieldValue fv) -> Date.valueOf(fv.getStringValue()).toLocalDate(), | ||||||||||||||||||||||||||||||||||||||||||||||
| FieldValue.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| LocalDate.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (FieldValue fv) -> LocalTime.parse(fv.getStringValue()), | ||||||||||||||||||||||||||||||||||||||||||||||
| FieldValue.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| LocalTime.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| BigQueryTypeCoercionUtility::parseFieldValueToLocalDateTime, | ||||||||||||||||||||||||||||||||||||||||||||||
| FieldValue.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| LocalDateTime.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (FieldValue fv) -> parseFieldValueToLocalDateTime(fv).atOffset(ZoneOffset.UTC), | ||||||||||||||||||||||||||||||||||||||||||||||
| FieldValue.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| OffsetDateTime.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (FieldValue fv) -> parseFieldValueToLocalDateTime(fv).atZone(ZoneOffset.UTC), | ||||||||||||||||||||||||||||||||||||||||||||||
| FieldValue.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| ZonedDateTime.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion( | ||||||||||||||||||||||||||||||||||||||||||||||
| (FieldValue fv) -> parseFieldValueToLocalDateTime(fv).toInstant(ZoneOffset.UTC), | ||||||||||||||||||||||||||||||||||||||||||||||
| FieldValue.class, | ||||||||||||||||||||||||||||||||||||||||||||||
| Instant.class) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion(new TimestampToString()) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion(new TimeToString()) | ||||||||||||||||||||||||||||||||||||||||||||||
| .registerTypeCoercion((Long l) -> l != 0L, Long.class, Boolean.class) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -276,7 +349,9 @@ private static class TimeToString implements BigQueryCoercion<Time, String> { | |||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||
| public String coerce(Time value) { | ||||||||||||||||||||||||||||||||||||||||||||||
| return FORMATTER.format(value.toLocalTime()); | ||||||||||||||||||||||||||||||||||||||||||||||
| LocalTime lt = | ||||||||||||||||||||||||||||||||||||||||||||||
| Instant.ofEpochMilli(value.getTime()).atZone(ZoneId.systemDefault()).toLocalTime(); | ||||||||||||||||||||||||||||||||||||||||||||||
| return FORMATTER.format(lt); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -355,10 +430,11 @@ private static class LongToTimestamp implements BigQueryCoercion<Long, Timestamp | |||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||
| public Timestamp coerce(Long value) { | ||||||||||||||||||||||||||||||||||||||||||||||
| // Long value is in microseconds. All further calculations should account for the unit. | ||||||||||||||||||||||||||||||||||||||||||||||
| Instant instant = Instant.EPOCH.plus(value, ChronoUnit.MICROS); | ||||||||||||||||||||||||||||||||||||||||||||||
| // Timezone-agnostic conversion preserving exact point in time as mandated by JDBC spec | ||||||||||||||||||||||||||||||||||||||||||||||
| return Timestamp.from(instant); | ||||||||||||||||||||||||||||||||||||||||||||||
| LocalDateTime utcDateTime = instant.atOffset(ZoneOffset.UTC).toLocalDateTime(); | ||||||||||||||||||||||||||||||||||||||||||||||
| Timestamp ts = Timestamp.valueOf(utcDateTime); | ||||||||||||||||||||||||||||||||||||||||||||||
| ts.setNanos((int) ((value % 1_000_000) * 1000)); | ||||||||||||||||||||||||||||||||||||||||||||||
| return ts; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
432
to
438
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. Using @Override
public Timestamp coerce(Long value) {
Instant instant = Instant.EPOCH.plus(value, ChronoUnit.MICROS);
return Timestamp.from(instant);
} |
||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -390,17 +466,9 @@ private static class FieldValueToTime implements BigQueryCoercion<FieldValue, Ti | |||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||
| public Time coerce(FieldValue fieldValue) { | ||||||||||||||||||||||||||||||||||||||||||||||
| // Time ranges from 00:00:00 to 23:59:59.999999 in BigQuery | ||||||||||||||||||||||||||||||||||||||||||||||
| String strTime = fieldValue.getStringValue(); | ||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||
| LocalTime localTime = LocalTime.parse(strTime); | ||||||||||||||||||||||||||||||||||||||||||||||
| // Convert LocalTime to milliseconds of the day. This correctly preserves millisecond | ||||||||||||||||||||||||||||||||||||||||||||||
| // precision and truncates anything smaller | ||||||||||||||||||||||||||||||||||||||||||||||
| long millisOfDay = TimeUnit.NANOSECONDS.toMillis(localTime.toNanoOfDay()); | ||||||||||||||||||||||||||||||||||||||||||||||
| // Adjust by local timezone offset to ensure correct wall-clock representation with | ||||||||||||||||||||||||||||||||||||||||||||||
| // millisecond precision | ||||||||||||||||||||||||||||||||||||||||||||||
| long localMillis = TimeZoneCache.getLocalMillis(millisOfDay); | ||||||||||||||||||||||||||||||||||||||||||||||
| return new Time(localMillis); | ||||||||||||||||||||||||||||||||||||||||||||||
| return localTimeToTime(LocalTime.parse(strTime)); | ||||||||||||||||||||||||||||||||||||||||||||||
| } catch (java.time.format.DateTimeParseException e) { | ||||||||||||||||||||||||||||||||||||||||||||||
| IllegalArgumentException ex = | ||||||||||||||||||||||||||||||||||||||||||||||
| new IllegalArgumentException( | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -416,19 +484,10 @@ private static class FieldValueToTimestamp implements BigQueryCoercion<FieldValu | |||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||
| public Timestamp coerce(FieldValue fieldValue) { | ||||||||||||||||||||||||||||||||||||||||||||||
| String rawValue = fieldValue.getStringValue(); | ||||||||||||||||||||||||||||||||||||||||||||||
| // BigQuery DATETIME strings are formatted like "YYYY-MM-DD'T'HH:MM:SS.fffffffff" | ||||||||||||||||||||||||||||||||||||||||||||||
| // BigQuery TIMESTAMP strings are numeric epoch seconds. | ||||||||||||||||||||||||||||||||||||||||||||||
| if (rawValue.contains("T")) { | ||||||||||||||||||||||||||||||||||||||||||||||
| // It's a DATETIME string. | ||||||||||||||||||||||||||||||||||||||||||||||
| // Timestamp.valueOf() expects "yyyy-mm-dd hh:mm:ss.fffffffff" format. | ||||||||||||||||||||||||||||||||||||||||||||||
| if (rawValue.contains("T") || rawValue.contains(" ")) { | ||||||||||||||||||||||||||||||||||||||||||||||
| return Timestamp.valueOf(rawValue.replace('T', ' ')); | ||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||
| // It's a TIMESTAMP numeric string. | ||||||||||||||||||||||||||||||||||||||||||||||
| long microseconds = fieldValue.getTimestampValue(); | ||||||||||||||||||||||||||||||||||||||||||||||
| Instant instant = Instant.EPOCH.plus(microseconds, ChronoUnit.MICROS); | ||||||||||||||||||||||||||||||||||||||||||||||
| // Timezone-agnostic conversion preserving exact point in time as mandated by JDBC spec | ||||||||||||||||||||||||||||||||||||||||||||||
| return Timestamp.from(instant); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| return new LongToTimestamp().coerce(fieldValue.getTimestampValue()); | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+487
to
+490
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. This change is highly error-prone and will break parsing for BigQuery
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
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.
This optimization for
size == 1is highly problematic and introduces a bug when the array elements themselves are arrays (for example, whentargetClassisObject.classand the single element is anObject[]representing a struct, or when dealing with multi-dimensional arrays). IftargetClassisObject.classandfirstValisObject[],firstVal.getClass().getComponentType().equals(targetClass)evaluates totrue(Object.class == Object.class). The method will then returnfirstValdirectly (an array of length N representing the struct's fields) instead of returning a wrapped array of length 1 containingfirstValas its single element. This violates the JDBC contract forArray.getArray(). Please remove thissize == 1shortcut.