-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat: BigQueryTypeRegistry core matrix (Phase 1) #13951
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
Changes from all commits
436c18e
efb8327
c23e0ce
657852f
62800cf
94fd293
536df8b
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 |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| /* | ||
| * Copyright 2026 Google LLC | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package com.google.cloud.bigquery.jdbc; | ||
|
|
||
| import java.sql.Date; | ||
| import java.sql.Time; | ||
| import java.sql.Timestamp; | ||
| import java.time.Instant; | ||
| import java.time.LocalDate; | ||
| import java.time.LocalDateTime; | ||
| import java.time.LocalTime; | ||
| import java.time.ZoneId; | ||
|
|
||
| /** | ||
| * A highly optimized utility for bridging BigQuery's civil time and absolute time semantics to | ||
| * legacy JDBC Date/Time/Timestamp classes using JSR-310 timezone anchoring. | ||
| */ | ||
| final class BigQueryTimezoneUtility { | ||
|
|
||
| private BigQueryTimezoneUtility() {} | ||
|
|
||
| /** | ||
| * Converts a BigQuery civil DATETIME string into an absolute Timestamp by anchoring it to the | ||
| * provided timezone (or JVM default if null). | ||
| */ | ||
| public static Timestamp boxDateTime(String val, ZoneId zoneId) { | ||
| ZoneId targetZone = zoneId != null ? zoneId : ZoneId.systemDefault(); | ||
| String isoString = val.replace(' ', 'T'); | ||
| return Timestamp.from(LocalDateTime.parse(isoString).atZone(targetZone).toInstant()); | ||
| } | ||
|
|
||
| /** | ||
| * Converts a BigQuery civil DATE string into an absolute Date by anchoring it to midnight of the | ||
| * provided timezone (or JVM default if null). | ||
| */ | ||
| public static Date boxDate(String val, ZoneId zoneId) { | ||
| ZoneId targetZone = zoneId != null ? zoneId : ZoneId.systemDefault(); | ||
| return new Date(LocalDate.parse(val).atStartOfDay(targetZone).toInstant().toEpochMilli()); | ||
| } | ||
|
|
||
| /** | ||
| * Converts a BigQuery civil TIME string into an absolute Time by anchoring it to Jan 1, 1970 of | ||
| * the provided timezone (or JVM default if null). | ||
| */ | ||
| public static Time boxTime(String val, ZoneId zoneId) { | ||
| ZoneId targetZone = zoneId != null ? zoneId : ZoneId.systemDefault(); | ||
| return new Time( | ||
| LocalTime.parse(val) | ||
| .atDate(LocalDate.of(1970, 1, 1)) | ||
| .atZone(targetZone) | ||
| .toInstant() | ||
| .toEpochMilli()); | ||
| } | ||
|
|
||
| /** | ||
| * Converts a BigQuery absolute TIMESTAMP string into a legacy Timestamp. Because it is absolute, | ||
| * the Calendar timezone is explicitly ignored per JDBC 4.2 spec. | ||
| */ | ||
| public static Timestamp boxTimestamp(String val) { | ||
| if (val.contains("T") || val.contains("Z")) { | ||
| return Timestamp.from(Instant.parse(val)); | ||
| } | ||
| return Timestamp.valueOf(val); | ||
| } | ||
|
Comment on lines
+73
to
+78
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. BigQuery's canonical string representation for absolute public static Timestamp boxTimestamp(String val) {
if (val.endsWith(" UTC")) {
String isoString = val.substring(0, val.length() - 4).replace(' ', 'T') + 'Z';
return Timestamp.from(Instant.parse(isoString));
}
if (val.contains("T") || val.contains("Z")) {
return Timestamp.from(Instant.parse(val));
}
return Timestamp.valueOf(val);
} |
||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,317 @@ | ||||||||||||||||||||||
| /* | ||||||||||||||||||||||
| * Copyright 2026 Google LLC | ||||||||||||||||||||||
| * | ||||||||||||||||||||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||||||||||||||||||
| * you may not use this file except in compliance with the License. | ||||||||||||||||||||||
| * You may obtain a copy of the License at | ||||||||||||||||||||||
| * | ||||||||||||||||||||||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||||||||||||||||||||||
| * | ||||||||||||||||||||||
| * Unless required by applicable law or agreed to in writing, software | ||||||||||||||||||||||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||||||||||||||||||||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||||||||||||||||||
| * See the License for the specific language governing permissions and | ||||||||||||||||||||||
| * limitations under the License. | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| package com.google.cloud.bigquery.jdbc; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import com.google.cloud.bigquery.StandardSQLTypeName; | ||||||||||||||||||||||
| import com.google.cloud.bigquery.exception.BigQueryJdbcException; | ||||||||||||||||||||||
| import java.math.BigDecimal; | ||||||||||||||||||||||
| import java.sql.Array; | ||||||||||||||||||||||
| import java.sql.Date; | ||||||||||||||||||||||
| import java.sql.Struct; | ||||||||||||||||||||||
| import java.sql.Time; | ||||||||||||||||||||||
| import java.sql.Timestamp; | ||||||||||||||||||||||
| import java.sql.Types; | ||||||||||||||||||||||
| import java.time.Instant; | ||||||||||||||||||||||
| import java.time.LocalDate; | ||||||||||||||||||||||
| import java.time.LocalDateTime; | ||||||||||||||||||||||
| import java.time.LocalTime; | ||||||||||||||||||||||
| import java.time.OffsetDateTime; | ||||||||||||||||||||||
| import java.time.ZonedDateTime; | ||||||||||||||||||||||
| import java.util.Arrays; | ||||||||||||||||||||||
| import java.util.Map; | ||||||||||||||||||||||
| import java.util.concurrent.ConcurrentHashMap; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * A central, bidirectional engine for resolving and coercing types between JDBC, Java, and | ||||||||||||||||||||||
| * BigQuery. | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| final class BigQueryTypeRegistry { | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| private static final TypeDescriptor<?>[] DESCRIPTORS_BY_ORDINAL; | ||||||||||||||||||||||
| private static final Map<Class<?>, TypeDescriptor<?>> DESCRIPTORS_BY_CLASS; | ||||||||||||||||||||||
| private static final Map<Integer, TypeDescriptor<?>> DESCRIPTORS_BY_JDBC_TYPE; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| static { | ||||||||||||||||||||||
| int maxOrdinal = 0; | ||||||||||||||||||||||
| for (StandardSQLTypeName type : StandardSQLTypeName.values()) { | ||||||||||||||||||||||
| if (type.ordinal() > maxOrdinal) { | ||||||||||||||||||||||
| maxOrdinal = type.ordinal(); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| DESCRIPTORS_BY_ORDINAL = new TypeDescriptor<?>[maxOrdinal + 1]; | ||||||||||||||||||||||
|
Comment on lines
+48
to
+55
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. Since enum ordinals are 0-indexed and sequential, the maximum ordinal is guaranteed to be
Suggested change
|
||||||||||||||||||||||
| DESCRIPTORS_BY_CLASS = new ConcurrentHashMap<>(); | ||||||||||||||||||||||
| DESCRIPTORS_BY_JDBC_TYPE = new ConcurrentHashMap<>(); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // BOOL | ||||||||||||||||||||||
| register( | ||||||||||||||||||||||
| new TypeDescriptor<>( | ||||||||||||||||||||||
| Types.BOOLEAN, | ||||||||||||||||||||||
| Boolean.class, | ||||||||||||||||||||||
| StandardSQLTypeName.BOOL, | ||||||||||||||||||||||
| Arrays.asList(Boolean.class), | ||||||||||||||||||||||
| (val, targetClass, zone) -> { | ||||||||||||||||||||||
| if (val instanceof Boolean) return val; | ||||||||||||||||||||||
| if (val instanceof String) return Boolean.parseBoolean((String) val); | ||||||||||||||||||||||
| throw new BigQueryJdbcException("Cannot convert to BOOL: " + val); | ||||||||||||||||||||||
| })); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // STRING | ||||||||||||||||||||||
| register( | ||||||||||||||||||||||
| new TypeDescriptor<>( | ||||||||||||||||||||||
| Types.NVARCHAR, | ||||||||||||||||||||||
| String.class, | ||||||||||||||||||||||
| StandardSQLTypeName.STRING, | ||||||||||||||||||||||
| Arrays.asList(String.class), | ||||||||||||||||||||||
| (val, targetClass, zone) -> String.valueOf(val))); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // INT64 | ||||||||||||||||||||||
| register( | ||||||||||||||||||||||
| new TypeDescriptor<>( | ||||||||||||||||||||||
| Types.BIGINT, | ||||||||||||||||||||||
| Long.class, | ||||||||||||||||||||||
| StandardSQLTypeName.INT64, | ||||||||||||||||||||||
| Arrays.asList(Long.class, Integer.class, Short.class, Byte.class), | ||||||||||||||||||||||
| (val, targetClass, zone) -> { | ||||||||||||||||||||||
| long longVal; | ||||||||||||||||||||||
| if (val instanceof Number) longVal = ((Number) val).longValue(); | ||||||||||||||||||||||
| else if (val instanceof String) longVal = Long.parseLong((String) val); | ||||||||||||||||||||||
| else throw new BigQueryJdbcException("Cannot convert to INT64: " + val); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (targetClass == Integer.class) return (int) longVal; | ||||||||||||||||||||||
| if (targetClass == Short.class) return (short) longVal; | ||||||||||||||||||||||
| if (targetClass == Byte.class) return (byte) longVal; | ||||||||||||||||||||||
| return longVal; | ||||||||||||||||||||||
| })); | ||||||||||||||||||||||
|
Comment on lines
+88
to
+98
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. When converting an (val, targetClass, zone) -> {
long longVal;
if (val instanceof Number) longVal = ((Number) val).longValue();
else if (val instanceof String) longVal = Long.parseLong((String) val);
else throw new BigQueryJdbcException("Cannot convert to INT64: " + val);
if (targetClass == Integer.class) {
if (longVal < Integer.MIN_VALUE || longVal > Integer.MAX_VALUE) {
throw new BigQueryJdbcException("Loss of precision converting " + longVal + " to Integer");
}
return (int) longVal;
}
if (targetClass == Short.class) {
if (longVal < Short.MIN_VALUE || longVal > Short.MAX_VALUE) {
throw new BigQueryJdbcException("Loss of precision converting " + longVal + " to Short");
}
return (short) longVal;
}
if (targetClass == Byte.class) {
if (longVal < Byte.MIN_VALUE || longVal > Byte.MAX_VALUE) {
throw new BigQueryJdbcException("Loss of precision converting " + longVal + " to Byte");
}
return (byte) longVal;
}
return longVal;
})); |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // FLOAT64 | ||||||||||||||||||||||
| register( | ||||||||||||||||||||||
| new TypeDescriptor<>( | ||||||||||||||||||||||
| Types.DOUBLE, | ||||||||||||||||||||||
| Double.class, | ||||||||||||||||||||||
| StandardSQLTypeName.FLOAT64, | ||||||||||||||||||||||
| Arrays.asList(Double.class, Float.class), | ||||||||||||||||||||||
| (val, targetClass, zone) -> { | ||||||||||||||||||||||
| double doubleVal; | ||||||||||||||||||||||
| if (val instanceof Number) doubleVal = ((Number) val).doubleValue(); | ||||||||||||||||||||||
| else if (val instanceof String) doubleVal = Double.parseDouble((String) val); | ||||||||||||||||||||||
| else throw new BigQueryJdbcException("Cannot convert to FLOAT64: " + val); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (targetClass == Float.class) return (float) doubleVal; | ||||||||||||||||||||||
| return doubleVal; | ||||||||||||||||||||||
| })); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // NUMERIC | ||||||||||||||||||||||
| register( | ||||||||||||||||||||||
| new TypeDescriptor<>( | ||||||||||||||||||||||
| Types.NUMERIC, | ||||||||||||||||||||||
| BigDecimal.class, | ||||||||||||||||||||||
| StandardSQLTypeName.NUMERIC, | ||||||||||||||||||||||
| Arrays.asList(BigDecimal.class), | ||||||||||||||||||||||
| (val, targetClass, zone) -> { | ||||||||||||||||||||||
| if (val instanceof BigDecimal) return val; | ||||||||||||||||||||||
| if (val instanceof Number) return new BigDecimal(val.toString()); | ||||||||||||||||||||||
| if (val instanceof String) return new BigDecimal((String) val); | ||||||||||||||||||||||
| throw new BigQueryJdbcException("Cannot convert to NUMERIC: " + val); | ||||||||||||||||||||||
| })); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // --- TEMPORAL TYPES MOVED TO PHASE 2 PR --- | ||||||||||||||||||||||
| // DATE, DATETIME, TIMESTAMP, and TIME descriptors require BigQueryTemporalUtility | ||||||||||||||||||||||
| // which will be introduced in the stacked Phase 2 PR. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // BYTES | ||||||||||||||||||||||
| register( | ||||||||||||||||||||||
| new TypeDescriptor<>( | ||||||||||||||||||||||
| Types.VARBINARY, | ||||||||||||||||||||||
| byte[].class, | ||||||||||||||||||||||
| StandardSQLTypeName.BYTES, | ||||||||||||||||||||||
| Arrays.asList(byte[].class), | ||||||||||||||||||||||
| (val, targetClass, zone) -> { | ||||||||||||||||||||||
| if (val instanceof byte[]) return val; | ||||||||||||||||||||||
| throw new BigQueryJdbcException("Cannot convert to BYTES: " + val); | ||||||||||||||||||||||
| })); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // ARRAY | ||||||||||||||||||||||
| register( | ||||||||||||||||||||||
| new TypeDescriptor<>( | ||||||||||||||||||||||
| Types.ARRAY, | ||||||||||||||||||||||
| Array.class, | ||||||||||||||||||||||
| StandardSQLTypeName.ARRAY, | ||||||||||||||||||||||
| Arrays.asList(Array.class), | ||||||||||||||||||||||
| (val, targetClass, zone) -> { | ||||||||||||||||||||||
| if (val instanceof Array) return val; | ||||||||||||||||||||||
| throw new BigQueryJdbcException("Cannot convert to ARRAY: " + val); | ||||||||||||||||||||||
| })); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // STRUCT | ||||||||||||||||||||||
| register( | ||||||||||||||||||||||
| new TypeDescriptor<>( | ||||||||||||||||||||||
| Types.STRUCT, | ||||||||||||||||||||||
| Struct.class, | ||||||||||||||||||||||
| StandardSQLTypeName.STRUCT, | ||||||||||||||||||||||
| Arrays.asList(Struct.class), | ||||||||||||||||||||||
| (val, targetClass, zone) -> { | ||||||||||||||||||||||
| if (val instanceof Struct) return val; | ||||||||||||||||||||||
| throw new BigQueryJdbcException("Cannot convert to STRUCT: " + val); | ||||||||||||||||||||||
| })); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // JSON | ||||||||||||||||||||||
| register( | ||||||||||||||||||||||
| new TypeDescriptor<>( | ||||||||||||||||||||||
| Types.OTHER, | ||||||||||||||||||||||
| String.class, | ||||||||||||||||||||||
| StandardSQLTypeName.JSON, | ||||||||||||||||||||||
| Arrays.asList(com.google.gson.JsonObject.class), | ||||||||||||||||||||||
| (val, targetClass, zone) -> String.valueOf(val))); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // BIGNUMERIC | ||||||||||||||||||||||
| register( | ||||||||||||||||||||||
| new TypeDescriptor<>( | ||||||||||||||||||||||
| Types.NUMERIC, | ||||||||||||||||||||||
| BigDecimal.class, | ||||||||||||||||||||||
| StandardSQLTypeName.BIGNUMERIC, | ||||||||||||||||||||||
| Arrays.asList(BigDecimal.class), | ||||||||||||||||||||||
| (val, targetClass, zone) -> { | ||||||||||||||||||||||
| if (val instanceof BigDecimal) return val; | ||||||||||||||||||||||
| if (val instanceof Number) return new BigDecimal(val.toString()); | ||||||||||||||||||||||
| if (val instanceof String) return new BigDecimal((String) val); | ||||||||||||||||||||||
| throw new BigQueryJdbcException("Cannot convert to BIGNUMERIC: " + val); | ||||||||||||||||||||||
| })); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // GEOGRAPHY | ||||||||||||||||||||||
| register( | ||||||||||||||||||||||
| new TypeDescriptor<>( | ||||||||||||||||||||||
| Types.OTHER, | ||||||||||||||||||||||
| String.class, | ||||||||||||||||||||||
| StandardSQLTypeName.GEOGRAPHY, | ||||||||||||||||||||||
| Arrays.asList(String.class), | ||||||||||||||||||||||
| (val, targetClass, zone) -> String.valueOf(val))); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // INTERVAL | ||||||||||||||||||||||
| register( | ||||||||||||||||||||||
| new TypeDescriptor<>( | ||||||||||||||||||||||
| Types.OTHER, | ||||||||||||||||||||||
| String.class, | ||||||||||||||||||||||
| StandardSQLTypeName.INTERVAL, | ||||||||||||||||||||||
| Arrays.asList(String.class), | ||||||||||||||||||||||
| (val, targetClass, zone) -> String.valueOf(val))); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // RANGE | ||||||||||||||||||||||
| register( | ||||||||||||||||||||||
| new TypeDescriptor<>( | ||||||||||||||||||||||
| Types.OTHER, | ||||||||||||||||||||||
| String.class, | ||||||||||||||||||||||
| StandardSQLTypeName.RANGE, | ||||||||||||||||||||||
| Arrays.asList(String.class), | ||||||||||||||||||||||
| (val, targetClass, zone) -> String.valueOf(val))); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| private static void register(TypeDescriptor<?> descriptor) { | ||||||||||||||||||||||
| if (DESCRIPTORS_BY_ORDINAL[descriptor.getBqType().ordinal()] == null) { | ||||||||||||||||||||||
| DESCRIPTORS_BY_ORDINAL[descriptor.getBqType().ordinal()] = descriptor; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| DESCRIPTORS_BY_JDBC_TYPE.putIfAbsent(descriptor.getJdbcType(), descriptor); | ||||||||||||||||||||||
| for (Class<?> clazz : descriptor.getSupportedJavaTypes()) { | ||||||||||||||||||||||
| DESCRIPTORS_BY_CLASS.putIfAbsent(clazz, descriptor); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| private BigQueryTypeRegistry() {} | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * Returns the exact BigQuery StandardSQLTypeName for a given Java class. If no mapping is found, | ||||||||||||||||||||||
| * returns StandardSQLTypeName.STRING as a fallback. | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| public static StandardSQLTypeName toBigQueryType(Class<?> clazz) { | ||||||||||||||||||||||
| TypeDescriptor<?> descriptor = getDescriptorForClass(clazz); | ||||||||||||||||||||||
| if (descriptor != null) { | ||||||||||||||||||||||
| return descriptor.getBqType(); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| return StandardSQLTypeName.STRING; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** Returns the default Java target class for a given JDBC type constant. */ | ||||||||||||||||||||||
| public static Class<?> toJavaClass(int jdbcType) { | ||||||||||||||||||||||
| TypeDescriptor<?> descriptor = DESCRIPTORS_BY_JDBC_TYPE.get(jdbcType); | ||||||||||||||||||||||
| if (descriptor != null) { | ||||||||||||||||||||||
| return descriptor.getDefaultJavaClass(); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| return String.class; // fallback | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * Converts the input value to the target class type by looking up the target class descriptor. | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| @SuppressWarnings("unchecked") | ||||||||||||||||||||||
| public static <T> T convert(Object input, Class<T> targetClass) throws BigQueryJdbcException { | ||||||||||||||||||||||
| if (input == null) { | ||||||||||||||||||||||
| return null; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| TypeDescriptor<?> descriptor = getDescriptorForClass(targetClass); | ||||||||||||||||||||||
| if (descriptor == null) { | ||||||||||||||||||||||
| throw new BigQueryJdbcException("Unsupported target class: " + targetClass.getName()); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| return (T) descriptor.convert(input, targetClass, null); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+259
to
+268
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. We can optimize the public static <T> T convert(Object input, Class<T> targetClass) throws BigQueryJdbcException {
if (input == null) {
return null;
}
if (targetClass.isInstance(input)) {
return targetClass.cast(input);
}
TypeDescriptor<?> descriptor = getDescriptorForClass(targetClass);
if (descriptor == null) {
throw new BigQueryJdbcException("Unsupported target class: " + targetClass.getName());
}
return (T) descriptor.convert(input, targetClass, null);
} |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * High-performance hotpath convert for ResultSets. Converts the input value using the default | ||||||||||||||||||||||
| * mapping for the given BigQuery type via O(1) array indexing. | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| public static Object convert(Object input, StandardSQLTypeName bqType, ZoneId zoneId) | ||||||||||||||||||||||
| throws BigQueryJdbcException { | ||||||||||||||||||||||
| if (input == null) return null; | ||||||||||||||||||||||
| int ordinal = bqType.ordinal(); | ||||||||||||||||||||||
| if (ordinal >= DESCRIPTORS_BY_ORDINAL.length || DESCRIPTORS_BY_ORDINAL[ordinal] == null) { | ||||||||||||||||||||||
| throw new BigQueryJdbcException("No type descriptor registered for BigQuery type: " + bqType); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| TypeDescriptor<?> descriptor = DESCRIPTORS_BY_ORDINAL[ordinal]; | ||||||||||||||||||||||
| return descriptor.convert(input, descriptor.getDefaultJavaClass(), zoneId); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * High-performance hotpath convert for ResultSets. Converts the input value to the target class | ||||||||||||||||||||||
| * using the descriptor for the given BigQuery type via O(1) array indexing. | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| @SuppressWarnings("unchecked") | ||||||||||||||||||||||
| public static <T> T convert( | ||||||||||||||||||||||
| Object input, StandardSQLTypeName bqType, Class<T> targetClass, ZoneId zoneId) | ||||||||||||||||||||||
| throws BigQueryJdbcException { | ||||||||||||||||||||||
| if (input == null) return null; | ||||||||||||||||||||||
| int ordinal = bqType.ordinal(); | ||||||||||||||||||||||
| if (ordinal >= DESCRIPTORS_BY_ORDINAL.length || DESCRIPTORS_BY_ORDINAL[ordinal] == null) { | ||||||||||||||||||||||
| throw new BigQueryJdbcException("No type descriptor registered for BigQuery type: " + bqType); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| return (T) DESCRIPTORS_BY_ORDINAL[ordinal].convert(input, targetClass, zoneId); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| private static TypeDescriptor<?> getDescriptorForClass(Class<?> clazz) { | ||||||||||||||||||||||
| TypeDescriptor<?> descriptor = DESCRIPTORS_BY_CLASS.get(clazz); | ||||||||||||||||||||||
|
Comment on lines
+301
to
+302
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. Since
Suggested change
|
||||||||||||||||||||||
| if (descriptor != null) { | ||||||||||||||||||||||
| return descriptor; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| // Fallback logic for subclasses/interfaces (O(N) initial lookup) | ||||||||||||||||||||||
| for (Map.Entry<Class<?>, TypeDescriptor<?>> entry : DESCRIPTORS_BY_CLASS.entrySet()) { | ||||||||||||||||||||||
| if (entry.getKey().isAssignableFrom(clazz)) { | ||||||||||||||||||||||
| TypeDescriptor<?> matchedDescriptor = entry.getValue(); | ||||||||||||||||||||||
| // Cache the result in the ConcurrentHashMap to turn subsequent subclass lookups into O(1) | ||||||||||||||||||||||
| DESCRIPTORS_BY_CLASS.putIfAbsent(clazz, matchedDescriptor); | ||||||||||||||||||||||
| return matchedDescriptor; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| return null; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
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.
When converting or shifting timezone fields for JDBC types like
java.sql.Time, use legacyCalendarmanipulation instead ofjava.timeAPIs to ensure Daylight Saving Time (DST) is handled properly.References