From 233a077a4601eeab1c4f2cc2e0d911864e03b457 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Wed, 12 Aug 2026 19:38:04 -0400 Subject: [PATCH 01/17] refactor(jdbc): re-route parameter binding to BigQueryTypeRegistry --- .../jdbc/BigQueryCallableStatement.java | 18 +++++++++--------- .../jdbc/BigQueryParameterHandler.java | 6 +++--- .../jdbc/BigQueryPreparedStatement.java | 4 ++-- .../bigquery/jdbc/BigQueryTypeRegistry.java | 10 ++++++++++ 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCallableStatement.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCallableStatement.java index 16fbfa55c390..7af80a65e27c 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCallableStatement.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCallableStatement.java @@ -507,7 +507,7 @@ public void registerOutParameter(int parameterIndex, int sqlType) throws SQLExce this.parameterHandler.setParameter( parameterIndex, null, - BigQueryJdbcTypeMappings.getJavaType(sqlType), + BigQueryTypeRegistry.toJavaClass(sqlType), BigQueryParameterHandler.BigQueryStatementParameterType.OUT, -1); } @@ -519,7 +519,7 @@ public void registerOutParameter(String parameterName, int sqlType) throws SQLEx this.parameterHandler.setParameter( parameterName, null, - BigQueryJdbcTypeMappings.getJavaType(sqlType), + BigQueryTypeRegistry.toJavaClass(sqlType), BigQueryParameterHandler.BigQueryStatementParameterType.OUT, -1); } @@ -540,7 +540,7 @@ public void registerOutParameter(int parameterIndex, int sqlType, int scale) thr this.parameterHandler.setParameter( parameterIndex, null, - BigQueryJdbcTypeMappings.getJavaType(sqlType), + BigQueryTypeRegistry.toJavaClass(sqlType), BigQueryParameterHandler.BigQueryStatementParameterType.OUT, scale); } @@ -572,7 +572,7 @@ public void registerOutParameter(String parameterName, int sqlType, int scale) this.parameterHandler.setParameter( parameterName, null, - BigQueryJdbcTypeMappings.getJavaType(sqlType), + BigQueryTypeRegistry.toJavaClass(sqlType), BigQueryParameterHandler.BigQueryStatementParameterType.OUT, scale); } @@ -778,7 +778,7 @@ public void setNString(String parameterName, String value) throws SQLException { @Override public void setNull(String parameterName, int sqlType) throws SQLException { checkClosed(); - Class javaType = BigQueryJdbcTypeMappings.getJavaType(sqlType); + Class javaType = BigQueryTypeRegistry.toJavaClass(sqlType); if (javaType == null) { javaType = String.class; } @@ -812,8 +812,8 @@ public void setObject(String parameterName, Object value, int targetSqlType) thr this.parameterHandler.setParameter( parameterName, value, value.getClass(), BigQueryStatementParameterType.IN, 0); StandardSQLTypeName sqlType = this.parameterHandler.getSqlType(parameterName); - if (BigQueryJdbcTypeMappings.standardSQLToJavaSqlTypesMapping.containsKey(sqlType)) { - int javaSqlType = BigQueryJdbcTypeMappings.standardSQLToJavaSqlTypesMapping.get(sqlType); + if (sqlType != null) { + int javaSqlType = BigQueryTypeRegistry.toJdbcType(sqlType); if (javaSqlType != targetSqlType) { throw new BigQueryJdbcSqlFeatureNotSupportedException( String.format("Unsupported sql type:%s ", targetSqlType)); @@ -835,8 +835,8 @@ public void setObject(String parameterName, Object value, int targetSqlType, int this.parameterHandler.setParameter( parameterName, value, value.getClass(), BigQueryStatementParameterType.IN, scaleOrLength); StandardSQLTypeName sqlType = this.parameterHandler.getSqlType(parameterName); - if (BigQueryJdbcTypeMappings.standardSQLToJavaSqlTypesMapping.containsKey(sqlType)) { - int javaSqlType = BigQueryJdbcTypeMappings.standardSQLToJavaSqlTypesMapping.get(sqlType); + if (sqlType != null) { + int javaSqlType = BigQueryTypeRegistry.toJdbcType(sqlType); if (javaSqlType != targetSqlType) { throw new BigQueryJdbcSqlFeatureNotSupportedException( String.format("Unsupported sql type:%s ", targetSqlType)); diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandler.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandler.java index c95b90ae6c05..e40e3fcf28ef 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandler.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandler.java @@ -128,7 +128,7 @@ void setParameter(int parameterIndex, Object value, Class type) parameter.setIndex(parameterIndex); parameter.setValue(value); parameter.setType(type); - parameter.setSqlType(BigQueryJdbcTypeMappings.classToType(type)); + parameter.setSqlType(BigQueryTypeRegistry.toBigQueryType(type)); parameter.setParamName(""); parameter.setParamType(BigQueryStatementParameterType.UNSPECIFIED); parameter.setScale(-1); @@ -208,7 +208,7 @@ void setParameter( } parameter.setValue(value); parameter.setType(type); - parameter.setSqlType(BigQueryJdbcTypeMappings.classToType(type)); + parameter.setSqlType(BigQueryTypeRegistry.toBigQueryType(type)); parameter.setParamName(paramName); parameter.setParamType(paramType); parameter.setScale(scale); @@ -243,7 +243,7 @@ void setParameter( parameter.setIndex(parameterIndex); parameter.setValue(value); parameter.setType(type); - parameter.setSqlType(BigQueryJdbcTypeMappings.classToType(type)); + parameter.setSqlType(BigQueryTypeRegistry.toBigQueryType(type)); parameter.setParamName(""); parameter.setParamType(paramType); parameter.setScale(scale); diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryPreparedStatement.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryPreparedStatement.java index 61faefb3fc5c..a5b40a8e2d34 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryPreparedStatement.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryPreparedStatement.java @@ -136,7 +136,7 @@ public void clearParameters() { @Override public void setNull(int parameterIndex, int sqlType) throws SQLException { checkClosed(); - Class javaType = BigQueryJdbcTypeMappings.getJavaType(sqlType); + Class javaType = BigQueryTypeRegistry.toJavaClass(sqlType); this.parameterHandler.setParameter(parameterIndex, null, javaType); } @@ -248,7 +248,7 @@ public void setObject(int parameterIndex, Object value, int targetSqlType) throw if (setTemporalObject(parameterIndex, value)) { return; } - Class javaType = BigQueryJdbcTypeMappings.getJavaType(targetSqlType); + Class javaType = BigQueryTypeRegistry.toJavaClass(targetSqlType); this.parameterHandler.setParameter(parameterIndex, value, javaType); } diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java index c2142072db5d..396816c381b1 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java @@ -361,6 +361,16 @@ public static StandardSQLTypeName toBigQueryType(Class clazz) { } /** Returns the default Java target class for a given JDBC type constant. */ + /** Returns the JDBC Type constant for a given BigQuery type. */ + public static int toJdbcType(StandardSQLTypeName bqType) { + if (bqType == null) return java.sql.Types.OTHER; + int ordinal = bqType.ordinal(); + if (ordinal >= DESCRIPTORS_BY_ORDINAL.length || DESCRIPTORS_BY_ORDINAL[ordinal] == null) { + return java.sql.Types.OTHER; + } + return DESCRIPTORS_BY_ORDINAL[ordinal].getJdbcType(); + } + public static Class toJavaClass(int jdbcType) { TypeDescriptor descriptor = DESCRIPTORS_BY_JDBC_TYPE.get(jdbcType); if (descriptor != null) { From 20db3580a561144dcbba2a1532c762c975c71c5c Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Wed, 12 Aug 2026 19:48:20 -0400 Subject: [PATCH 02/17] refactor(jdbc): migrate metadata mappings to TypeRegistry --- .../jdbc/BigQueryDatabaseMetaData.java | 4 +- .../jdbc/BigQueryParameterMetaData.java | 17 ++--- .../jdbc/BigQueryResultSetMetadata.java | 13 ++-- .../bigquery/jdbc/BigQueryTypeRegistry.java | 65 +++++++++++++++++++ 4 files changed, 77 insertions(+), 22 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java index 911e5cd8654d..fec91467c430 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java @@ -48,7 +48,7 @@ import com.google.cloud.bigquery.TableDefinition; import com.google.cloud.bigquery.TableId; import com.google.cloud.bigquery.exception.BigQueryJdbcException; -import com.google.cloud.bigquery.jdbc.BigQueryJdbcTypeMappings.ColumnTypeInfo; +import com.google.cloud.bigquery.jdbc.BigQueryTypeRegistry.ColumnTypeInfo; import com.google.cloud.bigquery.jdbc.utils.BigQueryJdbcVersionUtility; import io.opentelemetry.context.Context; import java.io.ByteArrayOutputStream; @@ -3976,7 +3976,7 @@ private ColumnTypeInfo getColumnTypeInfoForSqlType(StandardSQLTypeName bqType) { LOG.warning("Null BigQuery type encountered. Mapping to STRING."); return new ColumnTypeInfo(Types.NVARCHAR, "STRING", null, null, null); } - ColumnTypeInfo info = BigQueryJdbcTypeMappings.STANDARD_TYPE_INFO.get(bqType); + ColumnTypeInfo info = BigQueryTypeRegistry.getColumnTypeInfo(bqType); if (info == null) { LOG.warning("Unknown BigQuery type encountered: " + bqType.name() + ". Mapping to STRING."); return new ColumnTypeInfo(Types.NVARCHAR, "STRING", null, null, null); diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryParameterMetaData.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryParameterMetaData.java index d2b5dd4edae9..0b7ec146aaa2 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryParameterMetaData.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryParameterMetaData.java @@ -69,8 +69,7 @@ public int getPrecision(int param) throws SQLException { if (sqlType == null) { return 0; } - BigQueryJdbcTypeMappings.ColumnTypeInfo typeInfo = - BigQueryJdbcTypeMappings.STANDARD_TYPE_INFO.get(sqlType); + BigQueryTypeRegistry.ColumnTypeInfo typeInfo = BigQueryTypeRegistry.getColumnTypeInfo(sqlType); if (typeInfo != null && typeInfo.columnSize != null) { return typeInfo.columnSize; } @@ -84,8 +83,7 @@ public int getScale(int param) throws SQLException { if (sqlType == null) { return 0; } - BigQueryJdbcTypeMappings.ColumnTypeInfo typeInfo = - BigQueryJdbcTypeMappings.STANDARD_TYPE_INFO.get(sqlType); + BigQueryTypeRegistry.ColumnTypeInfo typeInfo = BigQueryTypeRegistry.getColumnTypeInfo(sqlType); if (typeInfo != null && typeInfo.decimalDigits != null) { return typeInfo.decimalDigits; } @@ -99,7 +97,7 @@ public int getParameterType(int param) throws SQLException { if (sqlType == null) { return Types.OTHER; } - Integer jdbcType = BigQueryJdbcTypeMappings.standardSQLToJavaSqlTypesMapping.get(sqlType); + Integer jdbcType = BigQueryTypeRegistry.toJdbcType(sqlType); if (jdbcType != null) { return jdbcType; } @@ -118,7 +116,7 @@ public String getParameterClassName(int param) throws SQLException { checkValidIndex(param); StandardSQLTypeName sqlType = getStandardSQLTypeName(param); if (sqlType != null) { - Class clazz = BigQueryJdbcTypeMappings.standardSQLToJavaTypeMapping.get(sqlType); + Class clazz = BigQueryTypeRegistry.toJavaClass(BigQueryTypeRegistry.toJdbcType(sqlType)); if (clazz != null) { return clazz.getName(); } @@ -162,12 +160,7 @@ private StandardSQLTypeName getStandardSQLTypeName(int param) { if (javaType == null) { return null; } - try { - return BigQueryJdbcTypeMappings.classToType(javaType); - } catch (SQLException ignored) { - // fall back to default - return null; - } + return BigQueryTypeRegistry.toBigQueryType(javaType); } @Override diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadata.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadata.java index c3fd8151d52c..d019aa45954d 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadata.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadata.java @@ -139,8 +139,7 @@ public int getPrecision(int column) { return precision.intValue(); } StandardSQLTypeName type = getStandardSQLTypeName(column); - BigQueryJdbcTypeMappings.ColumnTypeInfo typeInfo = - BigQueryJdbcTypeMappings.STANDARD_TYPE_INFO.get(type); + BigQueryTypeRegistry.ColumnTypeInfo typeInfo = BigQueryTypeRegistry.getColumnTypeInfo(type); if (typeInfo != null && typeInfo.columnSize != null) { return typeInfo.columnSize; } @@ -154,8 +153,7 @@ public int getScale(int column) { return scale.intValue(); } StandardSQLTypeName type = getStandardSQLTypeName(column); - BigQueryJdbcTypeMappings.ColumnTypeInfo typeInfo = - BigQueryJdbcTypeMappings.STANDARD_TYPE_INFO.get(type); + BigQueryTypeRegistry.ColumnTypeInfo typeInfo = BigQueryTypeRegistry.getColumnTypeInfo(type); if (typeInfo != null && typeInfo.decimalDigits != null) { return typeInfo.decimalDigits; } @@ -189,8 +187,7 @@ private StandardSQLTypeName getStandardSQLTypeName(int column) { @Override public int getColumnType(int column) { - return BigQueryJdbcTypeMappings.standardSQLToJavaSqlTypesMapping.get( - getStandardSQLTypeName(column)); + return BigQueryTypeRegistry.toJdbcType(getStandardSQLTypeName(column)); } @Override @@ -219,8 +216,8 @@ public String getColumnClassName(int column) { if (field.getMode() == Mode.REPEATED) { return java.sql.Array.class.getName(); } - return BigQueryJdbcTypeMappings.standardSQLToJavaTypeMapping - .get(field.getType().getStandardType()) + return BigQueryTypeRegistry.toJavaClass( + BigQueryTypeRegistry.toJdbcType(getStandardSQLTypeName(column))) .getName(); } diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java index 396816c381b1..5cef76ce78fb 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java @@ -18,6 +18,7 @@ import com.google.cloud.bigquery.StandardSQLTypeName; import com.google.cloud.bigquery.exception.BigQueryJdbcException; +import com.google.common.collect.ImmutableMap; import java.math.BigDecimal; import java.sql.Array; import java.sql.Date; @@ -441,4 +442,68 @@ private static TypeDescriptor getDescriptorForClass(Class clazz) { } return null; } + + static class ColumnTypeInfo { + final int jdbcType; + final String typeName; + final Integer columnSize; + final Integer decimalDigits; + final Integer numPrecRadix; + + ColumnTypeInfo( + int jdbcType, + String typeName, + Integer columnSize, + Integer decimalDigits, + Integer numPrecRadix) { + this.jdbcType = jdbcType; + this.typeName = typeName; + this.columnSize = columnSize; + this.decimalDigits = decimalDigits; + this.numPrecRadix = numPrecRadix; + } + } + + private static final Map STANDARD_TYPE_INFO = + ImmutableMap.builder() + .put(StandardSQLTypeName.INT64, new ColumnTypeInfo(Types.BIGINT, "INT64", 19, 0, 10)) + .put(StandardSQLTypeName.BOOL, new ColumnTypeInfo(Types.BOOLEAN, "BOOL", 1, null, null)) + .put( + StandardSQLTypeName.FLOAT64, + new ColumnTypeInfo(Types.DOUBLE, "FLOAT64", 15, null, 10)) + .put(StandardSQLTypeName.NUMERIC, new ColumnTypeInfo(Types.NUMERIC, "NUMERIC", 38, 9, 10)) + .put( + StandardSQLTypeName.BIGNUMERIC, + new ColumnTypeInfo(Types.NUMERIC, "BIGNUMERIC", 77, 38, 10)) + .put( + StandardSQLTypeName.STRING, + new ColumnTypeInfo(Types.NVARCHAR, "STRING", null, null, null)) + .put( + StandardSQLTypeName.TIMESTAMP, + new ColumnTypeInfo(Types.TIMESTAMP, "TIMESTAMP", 26, 6, null)) + .put( + StandardSQLTypeName.DATETIME, + new ColumnTypeInfo(Types.TIMESTAMP, "DATETIME", 26, 6, null)) + .put(StandardSQLTypeName.DATE, new ColumnTypeInfo(Types.DATE, "DATE", 10, 0, null)) + .put(StandardSQLTypeName.TIME, new ColumnTypeInfo(Types.TIME, "TIME", 15, 6, null)) + .put( + StandardSQLTypeName.GEOGRAPHY, + new ColumnTypeInfo(Types.OTHER, "GEOGRAPHY", null, null, null)) + .put(StandardSQLTypeName.JSON, new ColumnTypeInfo(Types.OTHER, "JSON", null, null, null)) + .put( + StandardSQLTypeName.INTERVAL, + new ColumnTypeInfo(Types.OTHER, "INTERVAL", null, null, null)) + .put( + StandardSQLTypeName.RANGE, new ColumnTypeInfo(Types.OTHER, "RANGE", null, null, null)) + .put( + StandardSQLTypeName.BYTES, + new ColumnTypeInfo(Types.VARBINARY, "BYTES", null, null, null)) + .put( + StandardSQLTypeName.STRUCT, + new ColumnTypeInfo(Types.STRUCT, "STRUCT", null, null, null)) + .build(); + + public static ColumnTypeInfo getColumnTypeInfo(StandardSQLTypeName bqType) { + return STANDARD_TYPE_INFO.get(bqType); + } } From 53729bb8ca94ea5e470b702d87b8ae05e8dab7ae Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Wed, 12 Aug 2026 20:08:59 -0400 Subject: [PATCH 03/17] refactor(jdbc): migrate Read Path to BigQueryTypeRegistry --- .../bigquery/jdbc/BigQueryArrowArray.java | 11 +++---- .../bigquery/jdbc/BigQueryArrowResultSet.java | 19 +++++------ .../bigquery/jdbc/BigQueryArrowStruct.java | 10 +++--- .../bigquery/jdbc/BigQueryBaseArray.java | 4 +-- .../bigquery/jdbc/BigQueryBaseResultSet.java | 32 +++++++++---------- .../bigquery/jdbc/BigQueryJsonArray.java | 12 +++---- .../bigquery/jdbc/BigQueryJsonResultSet.java | 6 ++-- .../bigquery/jdbc/BigQueryJsonStruct.java | 14 ++++---- 8 files changed, 54 insertions(+), 54 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowArray.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowArray.java index f25523a45e70..81ef40d04d0e 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowArray.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowArray.java @@ -28,8 +28,7 @@ * An implementation of {@link BigQueryBaseArray} used to represent Array values from Arrow data. */ class BigQueryArrowArray extends BigQueryBaseArray { - private static final BigQueryTypeCoercer BIGQUERY_TYPE_COERCER = - BigQueryTypeCoercionUtility.INSTANCE; + private JsonStringArrayList values; public BigQueryArrowArray(Field schema, JsonStringArrayList values) { @@ -43,7 +42,7 @@ public BigQueryArrowArray( } @Override - public Object getArray() { + public Object getArray() throws SQLException { LOG.finestTrace("getArray"); ensureValid(); if (values == null) { @@ -53,7 +52,7 @@ public Object getArray() { } @Override - public Object getArray(long index, int count) { + public Object getArray(long index, int count) throws SQLException { LOG.finestTrace("getArray"); ensureValid(); if (values == null) { @@ -98,12 +97,12 @@ public void free() { } @Override - Object getCoercedValue(int index) { + Object getCoercedValue(int index) throws SQLException { LOG.finestTrace("getCoercedValue"); Object value = this.values.get(index); return this.arrayOfStruct ? new BigQueryArrowStruct( schema.getSubFields(), (JsonStringHashMap) value, this.LOG.getArrowStructLogger()) - : BIGQUERY_TYPE_COERCER.coerceTo(getTargetClass(), value, this.LOG); + : BigQueryTypeRegistry.convert(value, getTargetClass()); } } diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java index 3123b6c09b40..15f6976436aa 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java @@ -357,7 +357,7 @@ public Object getObject(int columnIndex) throws SQLException { } if (this.isNested && columnIndex == 1) { - return this.bigQueryTypeCoercer.coerceTo(Integer.class, value, this.LOG); + return BigQueryTypeRegistry.convert(value, Integer.class); } if (this.isNested && columnIndex == 2) { @@ -371,7 +371,7 @@ public Object getObject(int columnIndex) throws SQLException { Class targetClass = BigQueryJdbcTypeMappings.standardSQLToJavaTypeMapping.get( arrayField.getType().getStandardType()); - return this.bigQueryTypeCoercer.coerceTo(targetClass, value, this.LOG); + return BigQueryTypeRegistry.convert(value, targetClass); } int fieldIndex = this.isNested ? 0 : columnIndex - 1; @@ -440,7 +440,7 @@ public Object getObject(int columnIndex) throws SQLException { Class targetClass = BigQueryJdbcTypeMappings.standardSQLToJavaTypeMapping.get( fieldSchema.getType().getStandardType()); - return this.bigQueryTypeCoercer.coerceTo(targetClass, value, this.LOG); + return BigQueryTypeRegistry.convert(value, targetClass); } } @@ -460,24 +460,25 @@ private StandardSQLTypeName getElementTypeFromValue(Object element) { return StandardSQLTypeName.STRING; } - private String formatRangeElement(Object element, StandardSQLTypeName elementType) { + private String formatRangeElement(Object element, StandardSQLTypeName elementType) + throws SQLException { if (element == null) { return "UNBOUNDED"; } switch (elementType) { case DATE: // Arrow gives DATE as an Integer (days since epoch) - Date date = this.bigQueryTypeCoercer.coerceTo(Date.class, (Integer) element, this.LOG); + Date date = BigQueryTypeRegistry.convert((Integer) element, Date.class); return date.toString(); case DATETIME: // Arrow gives DATETIME as a LocalDateTime Timestamp dtTs = - this.bigQueryTypeCoercer.coerceTo(Timestamp.class, (LocalDateTime) element, this.LOG); - return this.bigQueryTypeCoercer.coerceTo(String.class, dtTs, this.LOG); + BigQueryTypeRegistry.convert((LocalDateTime) element, Timestamp.class); + return BigQueryTypeRegistry.convert(dtTs, String.class); case TIMESTAMP: // Arrow gives TIMESTAMP as a Long (microseconds since epoch) - Timestamp ts = this.bigQueryTypeCoercer.coerceTo(Timestamp.class, (Long) element, this.LOG); - return this.bigQueryTypeCoercer.coerceTo(String.class, ts, this.LOG); + Timestamp ts = BigQueryTypeRegistry.convert((Long) element, Timestamp.class); + return BigQueryTypeRegistry.convert(ts, String.class); default: // Fallback for any other unexpected type return element.toString(); diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowStruct.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowStruct.java index e07406d996df..d9f0160b3f5e 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowStruct.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowStruct.java @@ -19,6 +19,7 @@ import static com.google.cloud.bigquery.jdbc.BigQueryBaseArray.isArray; import com.google.cloud.bigquery.Field; +import java.sql.SQLException; import com.google.cloud.bigquery.FieldList; import java.lang.reflect.Array; import java.util.ArrayList; @@ -30,8 +31,7 @@ * An implementation of {@link BigQueryBaseStruct} used to represent Struct values from Arrow data. */ class BigQueryArrowStruct extends BigQueryBaseStruct { - private static final BigQueryTypeCoercer BIGQUERY_TYPE_COERCER = - BigQueryTypeCoercionUtility.INSTANCE; + private final FieldList schema; @@ -54,7 +54,7 @@ FieldList getSchema() { } @Override - public Object[] getAttributes() { + public Object[] getAttributes() throws SQLException { LOG.finestTrace("getAttributes"); int size = this.schema.size(); Object[] attributes = (Object[]) Array.newInstance(Object.class, size); @@ -73,7 +73,7 @@ public Object[] getAttributes() { return attributes; } - private Object getValue(Field currentSchema, Object currentValue) { + private Object getValue(Field currentSchema, Object currentValue) throws SQLException { LOG.finestTrace("getValue"); if (isArray(currentSchema)) { return new BigQueryArrowArray( @@ -87,7 +87,7 @@ private Object getValue(Field currentSchema, Object currentValue) { Class targetClass = BigQueryJdbcTypeMappings.standardSQLToJavaTypeMapping.get( currentSchema.getType().getStandardType()); - return BIGQUERY_TYPE_COERCER.coerceTo(targetClass, currentValue, this.LOG); + return BigQueryTypeRegistry.convert(currentValue, targetClass); } } } diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseArray.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseArray.java index e001e35c6a21..d58847ab4946 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseArray.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseArray.java @@ -91,7 +91,7 @@ public final ResultSet getResultSet(long index, int count, Map> throw new BigQueryJdbcSqlFeatureNotSupportedException(CUSTOMER_TYPE_MAPPING_NOT_SUPPORTED); } - protected Object getArrayInternal(int fromIndex, int toIndexExclusive) { + protected Object getArrayInternal(int fromIndex, int toIndexExclusive) throws SQLException { LOG.finestTrace("getArrayInternal"); Class targetClass = getTargetClass(); int size = toIndexExclusive - fromIndex; @@ -149,7 +149,7 @@ protected Class getTargetClass() { this.schema.getType().getStandardType()); } - abstract Object getCoercedValue(int index); + abstract Object getCoercedValue(int index) throws SQLException; static boolean isArray(Field currentSchema) { return currentSchema.getMode() == REPEATED; diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseResultSet.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseResultSet.java index 9216732b49b2..ee52d7504738 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseResultSet.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseResultSet.java @@ -70,7 +70,7 @@ public abstract class BigQueryBaseResultSet extends BigQueryNoOpsResultSet private Job job; private SQLWarning warnings; private boolean warningsLoaded = false; - protected final BigQueryTypeCoercer bigQueryTypeCoercer = BigQueryTypeCoercionUtility.INSTANCE; + protected final SpanContext originalSpanContext; protected BigQueryBaseResultSet( @@ -297,7 +297,7 @@ public T getObject(int columnIndex, Class type) throws SQLException { if (value == null) { return null; } - return this.bigQueryTypeCoercer.coerceTo(type, value, this.LOG); + return BigQueryTypeRegistry.convert(value, type); } catch (RuntimeException e) { throw createCoercionException(columnIndex, type, e); } @@ -323,7 +323,7 @@ public String getString(int columnIndex) throws SQLException { LOG.finestTrace("getString"); try { Object value = getObject(columnIndex); - return this.bigQueryTypeCoercer.coerceTo(String.class, value, this.LOG); + return BigQueryTypeRegistry.convert(value, String.class); } catch (BigQueryJdbcCoercionNotFoundException e) { throw createCoercionException(columnIndex, String.class, e); } @@ -342,7 +342,7 @@ public boolean getBoolean(int columnIndex) throws SQLException { try { Object value = getObject(columnIndex); - return this.bigQueryTypeCoercer.coerceTo(Boolean.class, value, this.LOG); + return BigQueryTypeRegistry.convert(value, Boolean.class); } catch (BigQueryJdbcCoercionNotFoundException e) { throw createCoercionException(columnIndex, Boolean.class, e); } @@ -353,7 +353,7 @@ public byte getByte(int columnIndex) throws SQLException { LOG.finestTrace("getByte"); try { Object value = getObject(columnIndex); - return this.bigQueryTypeCoercer.coerceTo(Byte.class, value, this.LOG); + return BigQueryTypeRegistry.convert(value, Byte.class); } catch (BigQueryJdbcCoercionNotFoundException | BigQueryJdbcCoercionException e) { throw createCoercionException(columnIndex, Byte.class, e); } @@ -364,7 +364,7 @@ public short getShort(int columnIndex) throws SQLException { LOG.finestTrace("getShort"); try { Object value = getObject(columnIndex); - return this.bigQueryTypeCoercer.coerceTo(Short.class, value, this.LOG); + return BigQueryTypeRegistry.convert(value, Short.class); } catch (BigQueryJdbcCoercionNotFoundException | BigQueryJdbcCoercionException e) { throw createCoercionException(columnIndex, Short.class, e); } @@ -375,7 +375,7 @@ public int getInt(int columnIndex) throws SQLException { LOG.finestTrace("getInt"); try { Object value = getObject(columnIndex); - return this.bigQueryTypeCoercer.coerceTo(Integer.class, value, this.LOG); + return BigQueryTypeRegistry.convert(value, Integer.class); } catch (BigQueryJdbcCoercionNotFoundException | BigQueryJdbcCoercionException e) { throw createCoercionException(columnIndex, Integer.class, e); } @@ -386,7 +386,7 @@ public long getLong(int columnIndex) throws SQLException { LOG.finestTrace("getLong"); try { Object value = getObject(columnIndex); - return this.bigQueryTypeCoercer.coerceTo(Long.class, value, this.LOG); + return BigQueryTypeRegistry.convert(value, Long.class); } catch (BigQueryJdbcCoercionNotFoundException | BigQueryJdbcCoercionException e) { throw createCoercionException(columnIndex, Long.class, e); } @@ -397,7 +397,7 @@ public float getFloat(int columnIndex) throws SQLException { LOG.finestTrace("getFloat"); try { Object value = getObject(columnIndex); - return this.bigQueryTypeCoercer.coerceTo(Float.class, value, this.LOG); + return BigQueryTypeRegistry.convert(value, Float.class); } catch (BigQueryJdbcCoercionNotFoundException | BigQueryJdbcCoercionException e) { throw createCoercionException(columnIndex, Float.class, e); } @@ -408,7 +408,7 @@ public double getDouble(int columnIndex) throws SQLException { LOG.finestTrace("getDouble"); try { Object value = getObject(columnIndex); - return this.bigQueryTypeCoercer.coerceTo(Double.class, value, this.LOG); + return BigQueryTypeRegistry.convert(value, Double.class); } catch (BigQueryJdbcCoercionNotFoundException | BigQueryJdbcCoercionException e) { throw createCoercionException(columnIndex, Double.class, e); } @@ -421,7 +421,7 @@ public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException LOG.finestTrace("getBigDecimal"); try { Object value = getObject(columnIndex); - return this.bigQueryTypeCoercer.coerceTo(BigDecimal.class, value, this.LOG); + return BigQueryTypeRegistry.convert(value, BigDecimal.class); } catch (BigQueryJdbcCoercionNotFoundException | BigQueryJdbcCoercionException e) { throw createCoercionException(columnIndex, BigDecimal.class, e); } @@ -432,7 +432,7 @@ public byte[] getBytes(int columnIndex) throws SQLException { LOG.finestTrace("getBytes"); try { Object value = getObject(columnIndex); - return this.bigQueryTypeCoercer.coerceTo(byte[].class, value, this.LOG); + return BigQueryTypeRegistry.convert(value, byte[].class); } catch (BigQueryJdbcCoercionNotFoundException e) { throw createCoercionException(columnIndex, byte[].class, e); } @@ -443,7 +443,7 @@ public Date getDate(int columnIndex) throws SQLException { LOG.finestTrace("getDate"); try { Object value = getObject(columnIndex); - return this.bigQueryTypeCoercer.coerceTo(java.sql.Date.class, value, this.LOG); + return BigQueryTypeRegistry.convert(value, java.sql.Date.class); } catch (BigQueryJdbcCoercionNotFoundException e) { throw createCoercionException(columnIndex, java.sql.Date.class, e); } @@ -458,7 +458,7 @@ public Time getTime(int columnIndex) throws SQLException { } try { Object value = getObject(columnIndex); - return this.bigQueryTypeCoercer.coerceTo(java.sql.Time.class, value, this.LOG); + return BigQueryTypeRegistry.convert(value, java.sql.Time.class); } catch (BigQueryJdbcCoercionNotFoundException e) { throw createCoercionException(columnIndex, java.sql.Time.class, e); } @@ -473,7 +473,7 @@ public Timestamp getTimestamp(int columnIndex) throws SQLException { } try { Object value = getObject(columnIndex); - return this.bigQueryTypeCoercer.coerceTo(java.sql.Timestamp.class, value, this.LOG); + return BigQueryTypeRegistry.convert(value, java.sql.Timestamp.class); } catch (BigQueryJdbcCoercionNotFoundException e) { throw createCoercionException(columnIndex, java.sql.Timestamp.class, e); } @@ -484,7 +484,7 @@ public BigDecimal getBigDecimal(int columnIndex) throws SQLException { LOG.finestTrace("getBigDecimal"); try { Object value = getObject(columnIndex); - return this.bigQueryTypeCoercer.coerceTo(BigDecimal.class, value, this.LOG); + return BigQueryTypeRegistry.convert(value, BigDecimal.class); } catch (BigQueryJdbcCoercionNotFoundException | BigQueryJdbcCoercionException e) { throw createCoercionException(columnIndex, BigDecimal.class, e); } diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonArray.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonArray.java index 280c34aa15d6..a85fc3cce440 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonArray.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonArray.java @@ -25,13 +25,13 @@ import com.google.cloud.bigquery.FieldValue; import com.google.cloud.bigquery.Schema; import java.sql.ResultSet; +import java.sql.SQLException; import java.util.List; /** An implementation of {@link BigQueryBaseArray} used to represent Array values from Json data. */ @InternalApi class BigQueryJsonArray extends BigQueryBaseArray { - private static final BigQueryTypeCoercer BIGQUERY_TYPE_COERCER = - BigQueryTypeCoercionUtility.INSTANCE; + private List values; BigQueryJsonArray(Field schema, FieldValue values) { @@ -44,7 +44,7 @@ class BigQueryJsonArray extends BigQueryBaseArray { } @Override - public Object getArray() { + public Object getArray() throws SQLException { ensureValid(); LOG.finestTrace("getArray"); if (this.values == null) { @@ -54,7 +54,7 @@ public Object getArray() { } @Override - public Object getArray(long index, int count) { + public Object getArray(long index, int count) throws SQLException { ensureValid(); LOG.finestTrace("getArray"); if (this.values == null) { @@ -98,11 +98,11 @@ public void free() { } @Override - Object getCoercedValue(int index) { + Object getCoercedValue(int index) throws SQLException { FieldValue fieldValue = this.values.get(index); return this.arrayOfStruct ? new BigQueryJsonStruct( this.schema.getSubFields(), fieldValue, this.LOG.getJsonStructLogger()) - : BIGQUERY_TYPE_COERCER.coerceTo(getTargetClass(), fieldValue, this.LOG); + : BigQueryTypeRegistry.convert(fieldValue, getTargetClass()); } } diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSet.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSet.java index 0dbda843d1e1..a5bd1ded6107 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSet.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSet.java @@ -225,7 +225,7 @@ public Object getObject(int columnIndex) throws SQLException { } if (this.isNested && columnIndex == 1) { - return this.bigQueryTypeCoercer.coerceTo(Integer.class, value, this.LOG); + return BigQueryTypeRegistry.convert(value, Integer.class); } if (this.isNested && columnIndex == 2) { @@ -237,7 +237,7 @@ public Object getObject(int columnIndex) throws SQLException { Class targetClass = BigQueryJdbcTypeMappings.standardSQLToJavaTypeMapping.get( arrayField.getType().getStandardType()); - return this.bigQueryTypeCoercer.coerceTo(targetClass, value, this.LOG); + return BigQueryTypeRegistry.convert(value, targetClass); } int extraIndex = this.isNested ? 2 : 1; @@ -251,7 +251,7 @@ public Object getObject(int columnIndex) throws SQLException { Class targetClass = BigQueryJdbcTypeMappings.standardSQLToJavaTypeMapping.get( fieldSchema.getType().getStandardType()); - return this.bigQueryTypeCoercer.coerceTo(targetClass, value, this.LOG); + return BigQueryTypeRegistry.convert(value, targetClass); } } diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonStruct.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonStruct.java index ed39edbecf17..7943f229cb87 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonStruct.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonStruct.java @@ -22,6 +22,7 @@ import com.google.cloud.bigquery.Field; import com.google.cloud.bigquery.FieldList; import com.google.cloud.bigquery.FieldValue; +import java.sql.SQLException; import java.lang.reflect.Array; import java.util.List; @@ -30,8 +31,7 @@ */ @InternalApi class BigQueryJsonStruct extends BigQueryBaseStruct { - private static final BigQueryTypeCoercer BIGQUERY_TYPE_COERCER = - BigQueryTypeCoercionUtility.INSTANCE; + private final FieldList schema; private final List values; @@ -52,7 +52,7 @@ FieldList getSchema() { } @Override - public Object[] getAttributes() { + public Object[] getAttributes() throws SQLException { LOG.finestTrace("getAttributes"); int size = schema.size(); Object[] attributes = (Object[]) Array.newInstance(Object.class, size); @@ -66,18 +66,18 @@ public Object[] getAttributes() { return attributes; } - private Object getValue(Field currentSchema, FieldValue currentValue) { + private Object getValue(Field currentSchema, Object currentValue) throws SQLException { LOG.finestTrace("getValue"); if (isArray(currentSchema)) { - return new BigQueryJsonArray(currentSchema, currentValue, this.LOG.getJsonArrayLogger()); + return new BigQueryJsonArray(currentSchema, (FieldValue) currentValue, this.LOG.getJsonArrayLogger()); } else if (isStruct(currentSchema)) { return new BigQueryJsonStruct( - currentSchema.getSubFields(), currentValue, this.LOG.getJsonStructLogger()); + currentSchema.getSubFields(), (FieldValue) currentValue, this.LOG.getJsonStructLogger()); } else { Class targetClass = BigQueryJdbcTypeMappings.standardSQLToJavaTypeMapping.get( currentSchema.getType().getStandardType()); - return BIGQUERY_TYPE_COERCER.coerceTo(targetClass, currentValue, this.LOG); + return BigQueryTypeRegistry.convert(currentValue, targetClass); } } } From e26da00d6839e4889796e09e41e018ac6858e27d Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Wed, 12 Aug 2026 20:12:24 -0400 Subject: [PATCH 04/17] refactor(jdbc): migrate Read Path to BigQueryTypeRegistry --- .../google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java | 3 +-- .../com/google/cloud/bigquery/jdbc/BigQueryArrowStruct.java | 3 +-- .../com/google/cloud/bigquery/jdbc/BigQueryJsonStruct.java | 6 +++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java index 15f6976436aa..11d208f99cea 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java @@ -472,8 +472,7 @@ private String formatRangeElement(Object element, StandardSQLTypeName elementTyp return date.toString(); case DATETIME: // Arrow gives DATETIME as a LocalDateTime - Timestamp dtTs = - BigQueryTypeRegistry.convert((LocalDateTime) element, Timestamp.class); + Timestamp dtTs = BigQueryTypeRegistry.convert((LocalDateTime) element, Timestamp.class); return BigQueryTypeRegistry.convert(dtTs, String.class); case TIMESTAMP: // Arrow gives TIMESTAMP as a Long (microseconds since epoch) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowStruct.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowStruct.java index d9f0160b3f5e..c55114461733 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowStruct.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowStruct.java @@ -19,9 +19,9 @@ import static com.google.cloud.bigquery.jdbc.BigQueryBaseArray.isArray; import com.google.cloud.bigquery.Field; -import java.sql.SQLException; import com.google.cloud.bigquery.FieldList; import java.lang.reflect.Array; +import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import org.apache.arrow.vector.util.JsonStringArrayList; @@ -32,7 +32,6 @@ */ class BigQueryArrowStruct extends BigQueryBaseStruct { - private final FieldList schema; private final JsonStringHashMap values; diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonStruct.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonStruct.java index 7943f229cb87..56f0046759be 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonStruct.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonStruct.java @@ -22,8 +22,8 @@ import com.google.cloud.bigquery.Field; import com.google.cloud.bigquery.FieldList; import com.google.cloud.bigquery.FieldValue; -import java.sql.SQLException; import java.lang.reflect.Array; +import java.sql.SQLException; import java.util.List; /** @@ -32,7 +32,6 @@ @InternalApi class BigQueryJsonStruct extends BigQueryBaseStruct { - private final FieldList schema; private final List values; @@ -69,7 +68,8 @@ public Object[] getAttributes() throws SQLException { private Object getValue(Field currentSchema, Object currentValue) throws SQLException { LOG.finestTrace("getValue"); if (isArray(currentSchema)) { - return new BigQueryJsonArray(currentSchema, (FieldValue) currentValue, this.LOG.getJsonArrayLogger()); + return new BigQueryJsonArray( + currentSchema, (FieldValue) currentValue, this.LOG.getJsonArrayLogger()); } else if (isStruct(currentSchema)) { return new BigQueryJsonStruct( currentSchema.getSubFields(), (FieldValue) currentValue, this.LOG.getJsonStructLogger()); From 2ae39e2bb78283526844b8d0baaaa13772218718 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Wed, 12 Aug 2026 21:04:09 -0400 Subject: [PATCH 05/17] build fix --- .../jdbc/BigQueryParameterMetaData.java | 2 +- .../jdbc/BigQueryResultSetMetadata.java | 6 +- .../bigquery/jdbc/BigQueryTypeRegistry.java | 85 +++++++++++++++++-- .../jdbc/BigQueryDatabaseMetaDataTest.java | 2 +- 4 files changed, 80 insertions(+), 15 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryParameterMetaData.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryParameterMetaData.java index 0b7ec146aaa2..814d813ce646 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryParameterMetaData.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryParameterMetaData.java @@ -116,7 +116,7 @@ public String getParameterClassName(int param) throws SQLException { checkValidIndex(param); StandardSQLTypeName sqlType = getStandardSQLTypeName(param); if (sqlType != null) { - Class clazz = BigQueryTypeRegistry.toJavaClass(BigQueryTypeRegistry.toJdbcType(sqlType)); + Class clazz = BigQueryTypeRegistry.toJavaClass(sqlType); if (clazz != null) { return clazz.getName(); } diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadata.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadata.java index d019aa45954d..0a205a92fa2d 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadata.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadata.java @@ -211,14 +211,12 @@ public boolean isDefinitelyWritable(int column) { } @Override - public String getColumnClassName(int column) { + public String getColumnClassName(int column) throws SQLException { Field field = getField(column); if (field.getMode() == Mode.REPEATED) { return java.sql.Array.class.getName(); } - return BigQueryTypeRegistry.toJavaClass( - BigQueryTypeRegistry.toJdbcType(getStandardSQLTypeName(column))) - .getName(); + return BigQueryTypeRegistry.toJavaClass(getStandardSQLTypeName(column)).getName(); } // Wrapper methods: diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java index 5cef76ce78fb..6005a78ccce1 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java @@ -18,6 +18,7 @@ import com.google.cloud.bigquery.StandardSQLTypeName; import com.google.cloud.bigquery.exception.BigQueryJdbcException; +import com.google.cloud.bigquery.exception.BigQueryJdbcSqlFeatureNotSupportedException; import com.google.common.collect.ImmutableMap; import java.math.BigDecimal; import java.sql.Array; @@ -58,8 +59,8 @@ final class BigQueryTypeRegistry { register(createFloat64Descriptor()); register(createNumericDescriptor()); register(createDateDescriptor()); - register(createDatetimeDescriptor()); register(createTimestampDescriptor()); + register(createDatetimeDescriptor()); register(createTimeDescriptor()); register(createBytesDescriptor()); register(createArrayDescriptor()); @@ -372,12 +373,60 @@ public static int toJdbcType(StandardSQLTypeName bqType) { return DESCRIPTORS_BY_ORDINAL[ordinal].getJdbcType(); } - public static Class toJavaClass(int jdbcType) { - TypeDescriptor descriptor = DESCRIPTORS_BY_JDBC_TYPE.get(jdbcType); - if (descriptor != null) { - return descriptor.getDefaultJavaClass(); + /** + * Explicit mapping to resolve lossy reverse-lookups. Since multiple JDBC types (e.g., TINYINT, + * INTEGER) map to a single BigQuery type (INT64), this map ensures strict JDBC compliance by + * returning the exact Java class (e.g., Integer.class) expected for PreparedStatement binding. + */ + private static final Map> JDBC_TO_JAVA_CLASS_MAP = + ImmutableMap.>builder() + .put(Types.BIGINT, Long.class) + .put(Types.INTEGER, Integer.class) + .put(Types.SMALLINT, Short.class) + .put(Types.TINYINT, Byte.class) + .put(Types.BOOLEAN, Boolean.class) + .put(Types.DOUBLE, Double.class) + .put(Types.FLOAT, Float.class) + .put(Types.NUMERIC, BigDecimal.class) + .put(Types.VARCHAR, String.class) + .put(Types.NVARCHAR, String.class) + .put(Types.TIMESTAMP, Timestamp.class) + .put(Types.DATE, Date.class) + .put(Types.TIME, Time.class) + .put(Types.OTHER, String.class) + .put(Types.BINARY, byte[].class) + .put(Types.VARBINARY, byte[].class) + .put(Types.STRUCT, Struct.class) + .put(Types.BIT, Boolean.class) + .put(Types.ARRAY, Array.class) + .put(Types.NULL, String.class) + .build(); + + /** Returns the exact default Java Class for a given BigQuery type, avoiding lossy mappings. */ + public static Class toJavaClass(StandardSQLTypeName bqType) { + if (bqType == null) return String.class; + int ordinal = bqType.ordinal(); + if (ordinal >= DESCRIPTORS_BY_ORDINAL.length || DESCRIPTORS_BY_ORDINAL[ordinal] == null) { + return String.class; } - return String.class; // Legacy fallback + return DESCRIPTORS_BY_ORDINAL[ordinal].getDefaultJavaClass(); + } + + /** + * Returns the standard Java Class equivalent for a given JDBC SQL type. + * + * @param jdbcType the generic JDBC SQL type (e.g., {@link Types#INTEGER}) + * @return the corresponding Java Class (e.g., {@link Integer}) + * @throws BigQueryJdbcSqlFeatureNotSupportedException if the given SQL type is not supported + */ + public static Class toJavaClass(int jdbcType) + throws BigQueryJdbcSqlFeatureNotSupportedException { + Class clazz = JDBC_TO_JAVA_CLASS_MAP.get(jdbcType); + if (clazz == null) { + throw new BigQueryJdbcSqlFeatureNotSupportedException( + "Unsupported Java type for SQL type: " + jdbcType); + } + return clazz; } /** @@ -392,7 +441,12 @@ public static T convert(Object input, Class targetClass) throws BigQueryJ if (descriptor == null) { throw new BigQueryJdbcException("Unsupported target class: " + targetClass.getName()); } - return (T) descriptor.convert(input, targetClass, null); + try { + return (T) descriptor.convert(input, targetClass, null); + } catch (Exception e) { + throw new BigQueryJdbcException( + String.format("Failed to coerce value '%s' to %s", input, targetClass.getName()), e); + } } /** @@ -407,7 +461,12 @@ public static Object convert(Object input, StandardSQLTypeName bqType, ZoneId zo throw new BigQueryJdbcException("No type descriptor registered for BigQuery type: " + bqType); } TypeDescriptor descriptor = DESCRIPTORS_BY_ORDINAL[ordinal]; - return descriptor.convert(input, descriptor.getDefaultJavaClass(), zoneId); + try { + return descriptor.convert(input, descriptor.getDefaultJavaClass(), zoneId); + } catch (Exception e) { + throw new BigQueryJdbcException( + String.format("Failed to coerce value '%s' from BigQuery %s", input, bqType), e); + } } /** @@ -423,7 +482,15 @@ public static T convert( 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); + try { + return (T) DESCRIPTORS_BY_ORDINAL[ordinal].convert(input, targetClass, zoneId); + } catch (Exception e) { + throw new BigQueryJdbcException( + String.format( + "Failed to coerce value '%s' from BigQuery %s to %s", + input, bqType, targetClass.getName()), + e); + } } private static TypeDescriptor getDescriptorForClass(Class clazz) { diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaDataTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaDataTest.java index 6535fd20997c..77e3e08f1a70 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaDataTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaDataTest.java @@ -32,7 +32,7 @@ import com.google.api.gax.paging.Page; import com.google.cloud.bigquery.*; import com.google.cloud.bigquery.exception.BigQueryJdbcException; -import com.google.cloud.bigquery.jdbc.BigQueryJdbcTypeMappings.ColumnTypeInfo; +import com.google.cloud.bigquery.jdbc.BigQueryTypeRegistry.ColumnTypeInfo; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.trace.StatusCode; import io.opentelemetry.context.Context; From 962b62b58dd38d58f07d8028053b3d5e2ad3a353 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Wed, 12 Aug 2026 21:14:46 -0400 Subject: [PATCH 06/17] Merge branch 'jdbc-phase4-registry-integration' into jdbc-phase4-registry-read-path --- .../cloud/bigquery/jdbc/BigQueryArrowResultSet.java | 10 ++-------- .../cloud/bigquery/jdbc/BigQueryArrowStruct.java | 6 ++---- .../google/cloud/bigquery/jdbc/BigQueryBaseArray.java | 6 ++---- .../cloud/bigquery/jdbc/BigQueryJsonResultSet.java | 10 ++-------- .../google/cloud/bigquery/jdbc/BigQueryJsonStruct.java | 6 ++---- .../cloud/bigquery/jdbc/BigQueryTypeRegistry.java | 2 ++ .../bigquery/jdbc/BigQueryDatabaseMetaDataTest.java | 2 +- 7 files changed, 13 insertions(+), 29 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java index 11d208f99cea..660f363a3317 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java @@ -368,10 +368,7 @@ public Object getObject(int columnIndex) throws SQLException { (JsonStringHashMap) value, this.LOG.getArrowStructLogger()); } - Class targetClass = - BigQueryJdbcTypeMappings.standardSQLToJavaTypeMapping.get( - arrayField.getType().getStandardType()); - return BigQueryTypeRegistry.convert(value, targetClass); + return BigQueryTypeRegistry.convert(value, arrayField.getType().getStandardType(), null); } int fieldIndex = this.isNested ? 0 : columnIndex - 1; @@ -437,10 +434,7 @@ public Object getObject(int columnIndex) throws SQLException { // Strip trailing zeros to match JSON API and CLI output return ((BigDecimal) value).stripTrailingZeros(); } - Class targetClass = - BigQueryJdbcTypeMappings.standardSQLToJavaTypeMapping.get( - fieldSchema.getType().getStandardType()); - return BigQueryTypeRegistry.convert(value, targetClass); + return BigQueryTypeRegistry.convert(value, fieldSchema.getType().getStandardType(), null); } } diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowStruct.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowStruct.java index c55114461733..62a003a1fb28 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowStruct.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowStruct.java @@ -83,10 +83,8 @@ private Object getValue(Field currentSchema, Object currentValue) throws SQLExce (JsonStringHashMap) currentValue, this.LOG.getArrowStructLogger()); } else { - Class targetClass = - BigQueryJdbcTypeMappings.standardSQLToJavaTypeMapping.get( - currentSchema.getType().getStandardType()); - return BigQueryTypeRegistry.convert(currentValue, targetClass); + return BigQueryTypeRegistry.convert( + currentValue, currentSchema.getType().getStandardType(), null); } } } diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseArray.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseArray.java index d58847ab4946..d677f3182e8e 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseArray.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseArray.java @@ -65,8 +65,7 @@ public final String getBaseTypeName() { public final int getBaseType() { LOG.finestTrace("getBaseType"); ensureValid(); - return BigQueryJdbcTypeMappings.standardSQLToJavaSqlTypesMapping.get( - schema.getType().getStandardType()); + return BigQueryTypeRegistry.toJdbcType(schema.getType().getStandardType()); } @Override @@ -145,8 +144,7 @@ protected Class getTargetClass() { LOG.finestTrace("getTargetClass"); return this.arrayOfStruct ? Struct.class - : BigQueryJdbcTypeMappings.standardSQLToJavaTypeMapping.get( - this.schema.getType().getStandardType()); + : BigQueryTypeRegistry.toJavaClass(this.schema.getType().getStandardType()); } abstract Object getCoercedValue(int index) throws SQLException; diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSet.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSet.java index a5bd1ded6107..e08b87751fbb 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSet.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSet.java @@ -234,10 +234,7 @@ public Object getObject(int columnIndex) throws SQLException { return new BigQueryJsonStruct( arrayField.getSubFields(), value, this.LOG.getJsonStructLogger()); } - Class targetClass = - BigQueryJdbcTypeMappings.standardSQLToJavaTypeMapping.get( - arrayField.getType().getStandardType()); - return BigQueryTypeRegistry.convert(value, targetClass); + return BigQueryTypeRegistry.convert(value, arrayField.getType().getStandardType(), null); } int extraIndex = this.isNested ? 2 : 1; @@ -248,10 +245,7 @@ public Object getObject(int columnIndex) throws SQLException { return new BigQueryJsonStruct( fieldSchema.getSubFields(), value, this.LOG.getJsonStructLogger()); } else { - Class targetClass = - BigQueryJdbcTypeMappings.standardSQLToJavaTypeMapping.get( - fieldSchema.getType().getStandardType()); - return BigQueryTypeRegistry.convert(value, targetClass); + return BigQueryTypeRegistry.convert(value, fieldSchema.getType().getStandardType(), null); } } diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonStruct.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonStruct.java index 56f0046759be..c463419f6f1b 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonStruct.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonStruct.java @@ -74,10 +74,8 @@ private Object getValue(Field currentSchema, Object currentValue) throws SQLExce return new BigQueryJsonStruct( currentSchema.getSubFields(), (FieldValue) currentValue, this.LOG.getJsonStructLogger()); } else { - Class targetClass = - BigQueryJdbcTypeMappings.standardSQLToJavaTypeMapping.get( - currentSchema.getType().getStandardType()); - return BigQueryTypeRegistry.convert(currentValue, targetClass); + return BigQueryTypeRegistry.convert( + currentValue, currentSchema.getType().getStandardType(), null); } } } diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java index 6005a78ccce1..301be4856e2c 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java @@ -408,10 +408,12 @@ public static Class toJavaClass(StandardSQLTypeName bqType) { int ordinal = bqType.ordinal(); if (ordinal >= DESCRIPTORS_BY_ORDINAL.length || DESCRIPTORS_BY_ORDINAL[ordinal] == null) { return String.class; + } return DESCRIPTORS_BY_ORDINAL[ordinal].getDefaultJavaClass(); } + /** * Returns the standard Java Class equivalent for a given JDBC SQL type. * diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaDataTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaDataTest.java index 77e3e08f1a70..5b58b80f2765 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaDataTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaDataTest.java @@ -3280,7 +3280,7 @@ public void testMetadataAndResultSetMetadataTypeMappingConsistency(StandardSQLTy } ColumnTypeInfo metadataTypeInfo = dbMetadata.mapBigQueryTypeToJdbc(field); - Integer resultSetType = BigQueryJdbcTypeMappings.standardSQLToJavaSqlTypesMapping.get(type); + Integer resultSetType = BigQueryTypeRegistry.toJdbcType(type); assertNotNull(resultSetType, "ResultSet mapping should exist for " + type); assertEquals( From f9bf259979b672d8a98464a3cd9f5c99ce2a78b5 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Thu, 13 Aug 2026 11:40:03 -0400 Subject: [PATCH 07/17] address comments --- .../cloud/bigquery/jdbc/BigQueryCallableStatement.java | 3 --- .../cloud/bigquery/jdbc/BigQueryParameterMetaData.java | 10 ++-------- .../cloud/bigquery/jdbc/BigQueryTypeRegistry.java | 10 ++++++++++ 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCallableStatement.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCallableStatement.java index 7af80a65e27c..e8667001e8fb 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCallableStatement.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCallableStatement.java @@ -779,9 +779,6 @@ public void setNString(String parameterName, String value) throws SQLException { public void setNull(String parameterName, int sqlType) throws SQLException { checkClosed(); Class javaType = BigQueryTypeRegistry.toJavaClass(sqlType); - if (javaType == null) { - javaType = String.class; - } this.parameterHandler.setParameter( parameterName, null, javaType, BigQueryStatementParameterType.IN, 0); } diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryParameterMetaData.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryParameterMetaData.java index 814d813ce646..5dcdf6f7469a 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryParameterMetaData.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryParameterMetaData.java @@ -97,11 +97,7 @@ public int getParameterType(int param) throws SQLException { if (sqlType == null) { return Types.OTHER; } - Integer jdbcType = BigQueryTypeRegistry.toJdbcType(sqlType); - if (jdbcType != null) { - return jdbcType; - } - return Types.OTHER; + return BigQueryTypeRegistry.toJdbcType(sqlType); } @Override @@ -117,9 +113,7 @@ public String getParameterClassName(int param) throws SQLException { StandardSQLTypeName sqlType = getStandardSQLTypeName(param); if (sqlType != null) { Class clazz = BigQueryTypeRegistry.toJavaClass(sqlType); - if (clazz != null) { - return clazz.getName(); - } + return clazz.getName(); } if (this.parameterHandler == null) { return Object.class.getName(); diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java index 6005a78ccce1..03cb49b9ecc7 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java @@ -388,18 +388,28 @@ public static int toJdbcType(StandardSQLTypeName bqType) { .put(Types.DOUBLE, Double.class) .put(Types.FLOAT, Float.class) .put(Types.NUMERIC, BigDecimal.class) + .put(Types.DECIMAL, BigDecimal.class) .put(Types.VARCHAR, String.class) + .put(Types.CHAR, String.class) + .put(Types.LONGVARCHAR, String.class) .put(Types.NVARCHAR, String.class) + .put(Types.NCHAR, String.class) + .put(Types.LONGNVARCHAR, String.class) .put(Types.TIMESTAMP, Timestamp.class) .put(Types.DATE, Date.class) .put(Types.TIME, Time.class) .put(Types.OTHER, String.class) .put(Types.BINARY, byte[].class) .put(Types.VARBINARY, byte[].class) + .put(Types.LONGVARBINARY, byte[].class) .put(Types.STRUCT, Struct.class) .put(Types.BIT, Boolean.class) .put(Types.ARRAY, Array.class) .put(Types.NULL, String.class) + .put(Types.REAL, Float.class) + .put(Types.CLOB, String.class) + .put(Types.NCLOB, String.class) + .put(Types.BLOB, byte[].class) .build(); /** Returns the exact default Java Class for a given BigQuery type, avoiding lossy mappings. */ From aefad3b31445fc7072c7c4db31212f4995cfa1d5 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Thu, 13 Aug 2026 12:42:50 -0400 Subject: [PATCH 08/17] address bugs --- .../bigquery/jdbc/BigQueryJsonStruct.java | 7 ++-- .../jdbc/BigQueryTemporalUtility.java | 39 ++++++++++++++++++- .../bigquery/jdbc/BigQueryTypeRegistry.java | 38 ++++++++++++++++-- .../BigQueryJsonArrayOfPrimitivesTest.java | 8 ++-- .../bigquery/jdbc/BigQueryJsonStructTest.java | 2 +- 5 files changed, 81 insertions(+), 13 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonStruct.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonStruct.java index c463419f6f1b..c8129f41b88b 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonStruct.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonStruct.java @@ -65,14 +65,13 @@ public Object[] getAttributes() throws SQLException { return attributes; } - private Object getValue(Field currentSchema, Object currentValue) throws SQLException { + private Object getValue(Field currentSchema, FieldValue currentValue) throws SQLException { LOG.finestTrace("getValue"); if (isArray(currentSchema)) { - return new BigQueryJsonArray( - currentSchema, (FieldValue) currentValue, this.LOG.getJsonArrayLogger()); + return new BigQueryJsonArray(currentSchema, currentValue, this.LOG.getJsonArrayLogger()); } else if (isStruct(currentSchema)) { return new BigQueryJsonStruct( - currentSchema.getSubFields(), (FieldValue) currentValue, this.LOG.getJsonStructLogger()); + currentSchema.getSubFields(), currentValue, this.LOG.getJsonStructLogger()); } else { return BigQueryTypeRegistry.convert( currentValue, currentSchema.getType().getStandardType(), null); diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTemporalUtility.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTemporalUtility.java index b26cf78bac0a..db7912ff4b6a 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTemporalUtility.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTemporalUtility.java @@ -16,6 +16,7 @@ package com.google.cloud.bigquery.jdbc; +import java.math.BigDecimal; import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; @@ -94,6 +95,21 @@ public static Time boxTime(String val, ZoneId zoneId) { * the Calendar timezone is explicitly ignored per JDBC 4.2 spec. */ public static Timestamp boxTimestamp(String val) { + // Check if the value is a numeric float string (e.g. "1680174859.8202269" from JSON API) + try { + if (val.indexOf('-') < 0 + || (val.startsWith("-") + && val.indexOf('-', 1) < 0)) { // Quick check to ensure it's not a date string + BigDecimal bd = new BigDecimal(val); + long secondsLong = bd.longValue(); + int nanos = bd.remainder(BigDecimal.ONE).multiply(new BigDecimal(1_000_000_000)).intValue(); + Timestamp ts = new Timestamp(secondsLong * 1000L); + ts.setNanos(nanos); + return ts; + } + } catch (NumberFormatException ignored) { + } + String iso = val; // Handle the " UTC" suffix format if (iso.endsWith(" UTC")) { @@ -104,12 +120,33 @@ public static Timestamp boxTimestamp(String val) { if (iso.length() > 10 && iso.charAt(10) == ' ') { iso = iso.substring(0, 10) + 'T' + iso.substring(11); } + // If it doesn't have a timezone designator, assume UTC 'Z' + if (!iso.endsWith("Z") && !iso.contains("+") && iso.lastIndexOf('-') <= 10) { + iso = iso + "Z"; + } try { return Timestamp.from(Instant.parse(iso)); } catch (java.time.format.DateTimeParseException e) { // Fallback for non-standard formats - return Timestamp.valueOf(val); + String fallback = val; + if (fallback.indexOf('T') > 0) { + fallback = fallback.replace('T', ' '); + } + return Timestamp.valueOf(fallback); } } + + /** + * Converts milliseconds of the day to a local epoch millis anchored to 1970-01-01 in the given + * timezone. + */ + public static long getLocalMillis(long millisOfDay, ZoneId zoneId) { + ZoneId targetZone = zoneId != null ? zoneId : ZoneId.systemDefault(); + return LocalTime.ofNanoOfDay(millisOfDay * 1_000_000L) + .atDate(LocalDate.of(1970, 1, 1)) + .atZone(targetZone) + .toInstant() + .toEpochMilli(); + } } diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java index 933e1232f001..fe5899f9a1c0 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java @@ -16,6 +16,7 @@ package com.google.cloud.bigquery.jdbc; +import com.google.cloud.bigquery.FieldValue; import com.google.cloud.bigquery.StandardSQLTypeName; import com.google.cloud.bigquery.exception.BigQueryJdbcException; import com.google.cloud.bigquery.exception.BigQueryJdbcSqlFeatureNotSupportedException; @@ -35,6 +36,7 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Arrays; +import java.util.Base64; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -157,6 +159,11 @@ static TypeDescriptor createDateDescriptor() { else if (val instanceof java.util.Date) sqlDate = new Date(((java.util.Date) val).getTime()); else if (val instanceof LocalDate) sqlDate = Date.valueOf((LocalDate) val); + else if (val instanceof Integer) + sqlDate = Date.valueOf(LocalDate.ofEpochDay(((Integer) val).longValue())); + else if (val instanceof Long) sqlDate = Date.valueOf(LocalDate.ofEpochDay((Long) val)); + else if (val instanceof LocalDateTime) + sqlDate = Date.valueOf(((LocalDateTime) val).toLocalDate()); else if (val instanceof String) sqlDate = BigQueryTemporalUtility.boxDate((String) val, zone); else throw new BigQueryJdbcException("Cannot convert to DATE: " + val); @@ -205,6 +212,12 @@ else if (val instanceof OffsetDateTime) ts = Timestamp.from(((OffsetDateTime) val).toInstant()); else if (val instanceof ZonedDateTime) ts = Timestamp.from(((ZonedDateTime) val).toInstant()); + else if (val instanceof LocalDateTime) + ts = Timestamp.from(((LocalDateTime) val).toInstant(java.time.ZoneOffset.UTC)); + else if (val instanceof Long) + ts = + Timestamp.from( + Instant.EPOCH.plus((Long) val, java.time.temporal.ChronoUnit.MICROS)); else if (val instanceof String) ts = BigQueryTemporalUtility.boxTimestamp((String) val); else throw new BigQueryJdbcException("Cannot convert to TIMESTAMP: " + val); @@ -234,7 +247,14 @@ static TypeDescriptor createTimeDescriptor() { else if (val instanceof java.util.Date) sqlTime = new Time(((java.util.Date) val).getTime()); else if (val instanceof LocalTime) sqlTime = Time.valueOf((LocalTime) val); - else if (val instanceof String) + else if (val instanceof LocalDateTime) { + long millisOfDay = ((LocalDateTime) val).toLocalTime().toNanoOfDay() / 1_000_000; + sqlTime = new Time(BigQueryTemporalUtility.getLocalMillis(millisOfDay, zone)); + } else if (val instanceof Long) { + long millisOfDay = (Long) val / 1000; + // Align with civil time anchoring + sqlTime = new Time(BigQueryTemporalUtility.getLocalMillis(millisOfDay, zone)); + } else if (val instanceof String) sqlTime = BigQueryTemporalUtility.boxTime((String) val, zone); else throw new BigQueryJdbcException("Cannot convert to TIME: " + val); @@ -256,6 +276,7 @@ static TypeDescriptor createBytesDescriptor() { Arrays.asList(byte[].class), (val, targetClass, zone) -> { if (val instanceof byte[]) return val; + else if (val instanceof String) return Base64.getDecoder().decode((String) val); throw new BigQueryJdbcException("Cannot convert to BYTES: " + val); }); } @@ -418,12 +439,10 @@ public static Class toJavaClass(StandardSQLTypeName bqType) { int ordinal = bqType.ordinal(); if (ordinal >= DESCRIPTORS_BY_ORDINAL.length || DESCRIPTORS_BY_ORDINAL[ordinal] == null) { return String.class; - } return DESCRIPTORS_BY_ORDINAL[ordinal].getDefaultJavaClass(); } - /** * Returns the standard Java Class equivalent for a given JDBC SQL type. * @@ -449,6 +468,14 @@ public static T convert(Object input, Class targetClass) throws BigQueryJ if (input == null) { return null; } + if (input instanceof FieldValue) { + FieldValue fv = (FieldValue) input; + if (fv.isNull()) return null; + input = fv.getValue(); + } + if (targetClass.isInstance(input)) { + return (T) input; + } TypeDescriptor descriptor = getDescriptorForClass(targetClass); if (descriptor == null) { throw new BigQueryJdbcException("Unsupported target class: " + targetClass.getName()); @@ -468,6 +495,11 @@ public static T convert(Object input, Class targetClass) throws BigQueryJ public static Object convert(Object input, StandardSQLTypeName bqType, ZoneId zoneId) throws BigQueryJdbcException { if (input == null) return null; + if (input instanceof FieldValue) { + FieldValue fv = (FieldValue) input; + if (fv.isNull()) return null; + input = fv.getValue(); + } 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); diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJsonArrayOfPrimitivesTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJsonArrayOfPrimitivesTest.java index 537e20b60fea..30a66a2bf5fd 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJsonArrayOfPrimitivesTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJsonArrayOfPrimitivesTest.java @@ -127,10 +127,10 @@ public static Collection data() { TIMESTAMP, arraySchemaAndValue( TIMESTAMP, - "1680174859.8202269", - "1680261259.8202269", - "1680347659.8202269", - "1680434059.8202269"), + "1680174859.820227", + "1680261259.820227", + "1680347659.820227", + "1680434059.820227"), new Timestamp[] { Timestamp.valueOf(aTimeStamp), // 2023-03-30 16:44:19.82 Timestamp.valueOf(aTimeStamp.plusDays(1)), diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJsonStructTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJsonStructTest.java index ae074fa19e84..5c8deeab85e3 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJsonStructTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJsonStructTest.java @@ -241,7 +241,7 @@ public void structOfStructs() throws SQLException { public void structWithNullValue() throws SQLException { assertThat(structWithNullValue.getAttributes()) .isEqualTo( - Arrays.asList(0L, false, 0.0, null, null, null, null, null, null, null, null, null) + Arrays.asList(null, null, null, null, null, null, null, null, null, null, null, null) .toArray()); } From c5253821d969f74c3083ecf2d18bb50aa7d46a77 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Thu, 13 Aug 2026 14:03:09 -0400 Subject: [PATCH 09/17] fix date --- .../bigquery/jdbc/BigQueryArrowArray.java | 6 ++ .../bigquery/jdbc/BigQueryArrowResultSet.java | 9 ++ .../bigquery/jdbc/BigQueryArrowStruct.java | 6 ++ .../bigquery/jdbc/BigQueryTypeRegistry.java | 91 +++++++++++++++++-- 4 files changed, 103 insertions(+), 9 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowArray.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowArray.java index 81ef40d04d0e..2a8336768470 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowArray.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowArray.java @@ -19,8 +19,10 @@ import com.google.cloud.Tuple; import com.google.cloud.bigquery.Field; import com.google.cloud.bigquery.Schema; +import com.google.cloud.bigquery.StandardSQLTypeName; import java.sql.ResultSet; import java.sql.SQLException; +import java.time.LocalDate; import org.apache.arrow.vector.util.JsonStringArrayList; import org.apache.arrow.vector.util.JsonStringHashMap; @@ -100,6 +102,10 @@ public void free() { Object getCoercedValue(int index) throws SQLException { LOG.finestTrace("getCoercedValue"); Object value = this.values.get(index); + if (value instanceof Integer + && schema.getType().getStandardType() == StandardSQLTypeName.DATE) { + value = LocalDate.ofEpochDay(((Integer) value).longValue()); + } return this.arrayOfStruct ? new BigQueryArrowStruct( schema.getSubFields(), (JsonStringHashMap) value, this.LOG.getArrowStructLogger()) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java index 660f363a3317..120500cc2007 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java @@ -35,6 +35,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; +import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -42,6 +43,7 @@ import java.util.concurrent.Future; import org.apache.arrow.memory.BufferAllocator; import org.apache.arrow.memory.RootAllocator; +import org.apache.arrow.vector.DateDayVector; import org.apache.arrow.vector.FieldVector; import org.apache.arrow.vector.VectorLoader; import org.apache.arrow.vector.VectorSchemaRoot; @@ -340,6 +342,9 @@ private Object getObjectInternal(int columnIndex) throws SQLException { FieldVector currentColumn = this.vectorSchemaRoot.getVector(columnIndex - 1); // get the current row value = currentColumn.getObject(this.currentBatchRowIndex); + if (value instanceof Integer && currentColumn instanceof DateDayVector) { + value = LocalDate.ofEpochDay(((Integer) value).longValue()); + } } setWasNull(value); return value; @@ -368,6 +373,10 @@ public Object getObject(int columnIndex) throws SQLException { (JsonStringHashMap) value, this.LOG.getArrowStructLogger()); } + if (value instanceof Integer + && arrayField.getType().getStandardType() == StandardSQLTypeName.DATE) { + value = LocalDate.ofEpochDay(((Integer) value).longValue()); + } return BigQueryTypeRegistry.convert(value, arrayField.getType().getStandardType(), null); } diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowStruct.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowStruct.java index 62a003a1fb28..375c0619703a 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowStruct.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowStruct.java @@ -20,8 +20,10 @@ import com.google.cloud.bigquery.Field; import com.google.cloud.bigquery.FieldList; +import com.google.cloud.bigquery.StandardSQLTypeName; import java.lang.reflect.Array; import java.sql.SQLException; +import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import org.apache.arrow.vector.util.JsonStringArrayList; @@ -83,6 +85,10 @@ private Object getValue(Field currentSchema, Object currentValue) throws SQLExce (JsonStringHashMap) currentValue, this.LOG.getArrowStructLogger()); } else { + if (currentValue instanceof Integer + && currentSchema.getType().getStandardType() == StandardSQLTypeName.DATE) { + currentValue = LocalDate.ofEpochDay(((Integer) currentValue).longValue()); + } return BigQueryTypeRegistry.convert( currentValue, currentSchema.getType().getStandardType(), null); } diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java index fe5899f9a1c0..bde49b32855c 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java @@ -17,6 +17,7 @@ package com.google.cloud.bigquery.jdbc; import com.google.cloud.bigquery.FieldValue; +import com.google.cloud.bigquery.Range; import com.google.cloud.bigquery.StandardSQLTypeName; import com.google.cloud.bigquery.exception.BigQueryJdbcException; import com.google.cloud.bigquery.exception.BigQueryJdbcSqlFeatureNotSupportedException; @@ -28,17 +29,20 @@ import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; +import java.time.Duration; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.OffsetDateTime; +import java.time.Period; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Base64; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.apache.arrow.vector.PeriodDuration; /** * A central, bidirectional engine for resolving and coercing types between JDBC, Java, and @@ -82,6 +86,7 @@ static TypeDescriptor createBoolDescriptor() { Arrays.asList(Boolean.class), (val, targetClass, zone) -> { if (val instanceof Boolean) return val; + if (val instanceof Number) return ((Number) val).longValue() != 0; if (val instanceof String) return Boolean.parseBoolean((String) val); throw new BigQueryJdbcException("Cannot convert to BOOL: " + val); }); @@ -93,7 +98,51 @@ static TypeDescriptor createStringDescriptor() { String.class, StandardSQLTypeName.STRING, Arrays.asList(String.class), - (val, targetClass, zone) -> String.valueOf(val)); + (val, targetClass, zone) -> { + if (val == null) return null; + if (val instanceof byte[]) return Base64.getEncoder().encodeToString((byte[]) val); + if (val instanceof Range) { + Range range = (Range) val; + String start = + range.getStart().isNull() ? "UNBOUNDED" : range.getStart().getStringValue(); + String end = range.getEnd().isNull() ? "UNBOUNDED" : range.getEnd().getStringValue(); + return String.format("[%s, %s)", start, end); + } + if (val instanceof PeriodDuration) { + PeriodDuration pd = (PeriodDuration) val; + Period period = pd.getPeriod().normalized(); + StringBuilder builder = new StringBuilder(); + builder + .append(period.getYears()) + .append("-") + .append(period.getMonths()) + .append(" ") + .append(period.getDays()) + .append(" "); + Duration duration = pd.getDuration(); + if (duration.isNegative()) { + builder.append("-"); + duration = duration.negated(); + } + long hours = duration.toHours(); + duration = duration.minusHours(hours); + long minutes = duration.toMinutes(); + duration = duration.minusMinutes(minutes); + long seconds = duration.getSeconds(); + duration = duration.minusSeconds(seconds); + long microseconds = duration.toNanos() / 1000; + builder + .append(hours) + .append(":") + .append(minutes) + .append(":") + .append(seconds) + .append(".") + .append(microseconds); + return builder.toString().replaceFirst("--", "-"); + } + return String.valueOf(val); + }); } static TypeDescriptor createInt64Descriptor() { @@ -104,13 +153,35 @@ static TypeDescriptor createInt64Descriptor() { 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 (val instanceof Number) { + if (val instanceof BigDecimal) { + longVal = ((BigDecimal) val).longValueExact(); + } else { + longVal = ((Number) val).longValue(); + } + } else if (val instanceof String) { + longVal = Long.parseLong((String) val); + } else if (val instanceof Boolean) { + longVal = (Boolean) val ? 1L : 0L; + } 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; + if (targetClass == Integer.class) { + if (longVal > Integer.MAX_VALUE || longVal < Integer.MIN_VALUE) + throw new BigQueryJdbcException("Value out of range for Integer: " + longVal); + return (int) longVal; + } + if (targetClass == Short.class) { + if (longVal > Short.MAX_VALUE || longVal < Short.MIN_VALUE) + throw new BigQueryJdbcException("Value out of range for Short: " + longVal); + return (short) longVal; + } + if (targetClass == Byte.class) { + if (longVal > Byte.MAX_VALUE || longVal < Byte.MIN_VALUE) + throw new BigQueryJdbcException("Value out of range for Byte: " + longVal); + return (byte) longVal; + } return longVal; }); } @@ -125,6 +196,7 @@ static TypeDescriptor createFloat64Descriptor() { double doubleVal; if (val instanceof Number) doubleVal = ((Number) val).doubleValue(); else if (val instanceof String) doubleVal = Double.parseDouble((String) val); + else if (val instanceof Boolean) doubleVal = (Boolean) val ? 1.0 : 0.0; else throw new BigQueryJdbcException("Cannot convert to FLOAT64: " + val); if (targetClass == Float.class) return (float) doubleVal; @@ -142,6 +214,9 @@ static TypeDescriptor createNumericDescriptor() { if (val instanceof BigDecimal) return val; if (val instanceof Number) return new BigDecimal(val.toString()); if (val instanceof String) return new BigDecimal((String) val); + if (val instanceof Boolean) { + return (Boolean) val ? BigDecimal.ONE : BigDecimal.ZERO; + } throw new BigQueryJdbcException("Cannot convert to NUMERIC: " + val); }); } @@ -159,8 +234,6 @@ static TypeDescriptor createDateDescriptor() { else if (val instanceof java.util.Date) sqlDate = new Date(((java.util.Date) val).getTime()); else if (val instanceof LocalDate) sqlDate = Date.valueOf((LocalDate) val); - else if (val instanceof Integer) - sqlDate = Date.valueOf(LocalDate.ofEpochDay(((Integer) val).longValue())); else if (val instanceof Long) sqlDate = Date.valueOf(LocalDate.ofEpochDay((Long) val)); else if (val instanceof LocalDateTime) sqlDate = Date.valueOf(((LocalDateTime) val).toLocalDate()); From ece2ee66e123e0632e82fa33d090395ef64f6532 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Thu, 13 Aug 2026 14:40:36 -0400 Subject: [PATCH 10/17] fix date --- .../bigquery/jdbc/BigQueryTypeRegistry.java | 107 ++++++++++-------- 1 file changed, 60 insertions(+), 47 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java index bde49b32855c..b3cb54977fe1 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java @@ -101,45 +101,13 @@ static TypeDescriptor createStringDescriptor() { (val, targetClass, zone) -> { if (val == null) return null; if (val instanceof byte[]) return Base64.getEncoder().encodeToString((byte[]) val); - if (val instanceof Range) { - Range range = (Range) val; - String start = - range.getStart().isNull() ? "UNBOUNDED" : range.getStart().getStringValue(); - String end = range.getEnd().isNull() ? "UNBOUNDED" : range.getEnd().getStringValue(); - return String.format("[%s, %s)", start, end); + if (val instanceof Timestamp) { + return java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS") + .format(((Timestamp) val).toLocalDateTime()); } - if (val instanceof PeriodDuration) { - PeriodDuration pd = (PeriodDuration) val; - Period period = pd.getPeriod().normalized(); - StringBuilder builder = new StringBuilder(); - builder - .append(period.getYears()) - .append("-") - .append(period.getMonths()) - .append(" ") - .append(period.getDays()) - .append(" "); - Duration duration = pd.getDuration(); - if (duration.isNegative()) { - builder.append("-"); - duration = duration.negated(); - } - long hours = duration.toHours(); - duration = duration.minusHours(hours); - long minutes = duration.toMinutes(); - duration = duration.minusMinutes(minutes); - long seconds = duration.getSeconds(); - duration = duration.minusSeconds(seconds); - long microseconds = duration.toNanos() / 1000; - builder - .append(hours) - .append(":") - .append(minutes) - .append(":") - .append(seconds) - .append(".") - .append(microseconds); - return builder.toString().replaceFirst("--", "-"); + if (val instanceof Time) { + return java.time.format.DateTimeFormatter.ofPattern("HH:mm:ss.SSS") + .format(((Time) val).toLocalTime()); } return String.valueOf(val); }); @@ -154,11 +122,7 @@ static TypeDescriptor createInt64Descriptor() { (val, targetClass, zone) -> { long longVal; if (val instanceof Number) { - if (val instanceof BigDecimal) { - longVal = ((BigDecimal) val).longValueExact(); - } else { - longVal = ((Number) val).longValue(); - } + longVal = ((Number) val).longValue(); } else if (val instanceof String) { longVal = Long.parseLong((String) val); } else if (val instanceof Boolean) { @@ -234,7 +198,6 @@ static TypeDescriptor createDateDescriptor() { else if (val instanceof java.util.Date) sqlDate = new Date(((java.util.Date) val).getTime()); else if (val instanceof LocalDate) sqlDate = Date.valueOf((LocalDate) val); - else if (val instanceof Long) sqlDate = Date.valueOf(LocalDate.ofEpochDay((Long) val)); else if (val instanceof LocalDateTime) sqlDate = Date.valueOf(((LocalDateTime) val).toLocalDate()); else if (val instanceof String) @@ -317,7 +280,11 @@ static TypeDescriptor createTimeDescriptor() { Time sqlTime; if (val instanceof Time) sqlTime = (Time) val; - else if (val instanceof java.util.Date) + else if (val instanceof Timestamp) { + sqlTime = + Time.valueOf( + ((Timestamp) val).toInstant().atOffset(java.time.ZoneOffset.UTC).toLocalTime()); + } else if (val instanceof java.util.Date) sqlTime = new Time(((java.util.Date) val).getTime()); else if (val instanceof LocalTime) sqlTime = Time.valueOf((LocalTime) val); else if (val instanceof LocalDateTime) { @@ -416,7 +383,43 @@ static TypeDescriptor createIntervalDescriptor() { String.class, StandardSQLTypeName.INTERVAL, Arrays.asList(String.class), - (val, targetClass, zone) -> String.valueOf(val)); + (val, targetClass, zone) -> { + if (val == null) return null; + if (val instanceof PeriodDuration) { + PeriodDuration pd = (PeriodDuration) val; + Period period = pd.getPeriod().normalized(); + StringBuilder builder = new StringBuilder(); + builder + .append(period.getYears()) + .append("-") + .append(period.getMonths()) + .append(" ") + .append(period.getDays()) + .append(" "); + Duration duration = pd.getDuration(); + if (duration.isNegative()) { + builder.append("-"); + duration = duration.negated(); + } + long hours = duration.toHours(); + duration = duration.minusHours(hours); + long minutes = duration.toMinutes(); + duration = duration.minusMinutes(minutes); + long seconds = duration.getSeconds(); + duration = duration.minusSeconds(seconds); + long microseconds = duration.toNanos() / 1000; + builder + .append(hours) + .append(":") + .append(minutes) + .append(":") + .append(seconds) + .append(".") + .append(microseconds); + return builder.toString().replaceFirst("--", "-"); + } + return String.valueOf(val); + }); } static TypeDescriptor createRangeDescriptor() { @@ -425,7 +428,17 @@ static TypeDescriptor createRangeDescriptor() { String.class, StandardSQLTypeName.RANGE, Arrays.asList(String.class), - (val, targetClass, zone) -> String.valueOf(val)); + (val, targetClass, zone) -> { + if (val == null) return null; + if (val instanceof Range) { + Range range = (Range) val; + String start = + range.getStart().isNull() ? "UNBOUNDED" : range.getStart().getStringValue(); + String end = range.getEnd().isNull() ? "UNBOUNDED" : range.getEnd().getStringValue(); + return String.format("[%s, %s)", start, end); + } + return String.valueOf(val); + }); } private static void register(TypeDescriptor descriptor) { From df445f840252f9e84315e501556e4f625138b469 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Thu, 13 Aug 2026 17:30:34 -0400 Subject: [PATCH 11/17] fix temopral coercions --- .../bigquery/jdbc/BigQueryArrowResultSet.java | 4 +-- .../bigquery/jdbc/BigQueryTypeRegistry.java | 32 ++++++++++++------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java index 120500cc2007..86e82f1ce676 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java @@ -31,7 +31,6 @@ import io.opentelemetry.context.Scope; import java.io.IOException; import java.math.BigDecimal; -import java.sql.Date; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; @@ -471,8 +470,7 @@ private String formatRangeElement(Object element, StandardSQLTypeName elementTyp switch (elementType) { case DATE: // Arrow gives DATE as an Integer (days since epoch) - Date date = BigQueryTypeRegistry.convert((Integer) element, Date.class); - return date.toString(); + return LocalDate.ofEpochDay(((Integer) element).longValue()).toString(); case DATETIME: // Arrow gives DATETIME as a LocalDateTime Timestamp dtTs = BigQueryTypeRegistry.convert((LocalDateTime) element, Timestamp.class); diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java index b3cb54977fe1..13c2db8fdd5c 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java @@ -37,7 +37,9 @@ import java.time.OffsetDateTime; import java.time.Period; import java.time.ZoneId; +import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.Base64; import java.util.Map; @@ -102,12 +104,11 @@ static TypeDescriptor createStringDescriptor() { if (val == null) return null; if (val instanceof byte[]) return Base64.getEncoder().encodeToString((byte[]) val); if (val instanceof Timestamp) { - return java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS") + return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS") .format(((Timestamp) val).toLocalDateTime()); } if (val instanceof Time) { - return java.time.format.DateTimeFormatter.ofPattern("HH:mm:ss.SSS") - .format(((Time) val).toLocalTime()); + return DateTimeFormatter.ofPattern("HH:mm:ss.SSS").format(((Time) val).toLocalTime()); } return String.valueOf(val); }); @@ -122,6 +123,13 @@ static TypeDescriptor createInt64Descriptor() { (val, targetClass, zone) -> { long longVal; if (val instanceof Number) { + if (val instanceof BigDecimal) { + BigDecimal bd = (BigDecimal) val; + if (bd.compareTo(new BigDecimal(Long.MAX_VALUE)) > 0 + || bd.compareTo(new BigDecimal(Long.MIN_VALUE)) < 0) { + throw new BigQueryJdbcException("Value out of range for Long: " + bd); + } + } longVal = ((Number) val).longValue(); } else if (val instanceof String) { longVal = Long.parseLong((String) val); @@ -195,7 +203,10 @@ static TypeDescriptor createDateDescriptor() { // TODO(Phase 3): Add native JSR-310 fast-path to bypass boxing for LocalDate Date sqlDate; if (val instanceof Date) sqlDate = (Date) val; - else if (val instanceof java.util.Date) + else if (val instanceof Timestamp) { + sqlDate = + Date.valueOf(((Timestamp) val).toInstant().atOffset(ZoneOffset.UTC).toLocalDate()); + } else if (val instanceof java.util.Date) sqlDate = new Date(((java.util.Date) val).getTime()); else if (val instanceof LocalDate) sqlDate = Date.valueOf((LocalDate) val); else if (val instanceof LocalDateTime) @@ -249,7 +260,7 @@ else if (val instanceof OffsetDateTime) else if (val instanceof ZonedDateTime) ts = Timestamp.from(((ZonedDateTime) val).toInstant()); else if (val instanceof LocalDateTime) - ts = Timestamp.from(((LocalDateTime) val).toInstant(java.time.ZoneOffset.UTC)); + ts = Timestamp.from(((LocalDateTime) val).toInstant(ZoneOffset.UTC)); else if (val instanceof Long) ts = Timestamp.from( @@ -258,10 +269,8 @@ else if (val instanceof Long) else throw new BigQueryJdbcException("Cannot convert to TIMESTAMP: " + val); if (targetClass == Instant.class) return ts.toInstant(); - if (targetClass == OffsetDateTime.class) - return ts.toInstant().atOffset(java.time.ZoneOffset.UTC); - if (targetClass == ZonedDateTime.class) - return ts.toInstant().atZone(java.time.ZoneOffset.UTC); + if (targetClass == OffsetDateTime.class) return ts.toInstant().atOffset(ZoneOffset.UTC); + if (targetClass == ZonedDateTime.class) return ts.toInstant().atZone(ZoneOffset.UTC); return ts; }); } @@ -282,8 +291,9 @@ static TypeDescriptor createTimeDescriptor() { if (val instanceof Time) sqlTime = (Time) val; else if (val instanceof Timestamp) { sqlTime = - Time.valueOf( - ((Timestamp) val).toInstant().atOffset(java.time.ZoneOffset.UTC).toLocalTime()); + Time.valueOf(((Timestamp) val).toInstant().atOffset(ZoneOffset.UTC).toLocalTime()); + } else if (val instanceof java.sql.Date) { + throw new BigQueryJdbcException("Cannot convert to TIME: " + val); } else if (val instanceof java.util.Date) sqlTime = new Time(((java.util.Date) val).getTime()); else if (val instanceof LocalTime) sqlTime = Time.valueOf((LocalTime) val); From 69b73a8e06359a4112c2d4570728f5a8d0be2688 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Thu, 13 Aug 2026 17:37:46 -0400 Subject: [PATCH 12/17] optimize --- .../cloud/bigquery/jdbc/BigQueryTypeRegistry.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java index 13c2db8fdd5c..3795914fd8f3 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java @@ -56,6 +56,11 @@ final class BigQueryTypeRegistry { private static final Map, TypeDescriptor> DESCRIPTORS_BY_CLASS; private static final Map> DESCRIPTORS_BY_JDBC_TYPE; + private static final DateTimeFormatter TIMESTAMP_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS"); + private static final DateTimeFormatter TIME_FORMATTER = + DateTimeFormatter.ofPattern("HH:mm:ss.SSS"); + static { DESCRIPTORS_BY_ORDINAL = new TypeDescriptor[StandardSQLTypeName.values().length]; DESCRIPTORS_BY_CLASS = new ConcurrentHashMap<>(); @@ -104,11 +109,10 @@ static TypeDescriptor createStringDescriptor() { if (val == null) return null; if (val instanceof byte[]) return Base64.getEncoder().encodeToString((byte[]) val); if (val instanceof Timestamp) { - return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS") - .format(((Timestamp) val).toLocalDateTime()); + return TIMESTAMP_FORMATTER.format(((Timestamp) val).toLocalDateTime()); } if (val instanceof Time) { - return DateTimeFormatter.ofPattern("HH:mm:ss.SSS").format(((Time) val).toLocalTime()); + return TIME_FORMATTER.format(((Time) val).toLocalTime()); } return String.valueOf(val); }); From 4b8236359803bd77d88065dd884940b5366c16b1 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Thu, 13 Aug 2026 19:02:34 -0400 Subject: [PATCH 13/17] time to date --- .../com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java index 3795914fd8f3..e4a03eb72fa5 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java @@ -210,6 +210,8 @@ static TypeDescriptor createDateDescriptor() { else if (val instanceof Timestamp) { sqlDate = Date.valueOf(((Timestamp) val).toInstant().atOffset(ZoneOffset.UTC).toLocalDate()); + } else if (val instanceof java.sql.Time) { + throw new BigQueryJdbcException("Cannot convert to DATE: " + val); } else if (val instanceof java.util.Date) sqlDate = new Date(((java.util.Date) val).getTime()); else if (val instanceof LocalDate) sqlDate = Date.valueOf((LocalDate) val); From 16bcb80052614418d571336ec6f1bfaa5ed75622 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Mon, 24 Aug 2026 14:08:10 -0400 Subject: [PATCH 14/17] address comments --- .../jdbc/BigQueryCallableStatement.java | 28 +----- .../bigquery/jdbc/BigQueryTypeRegistry.java | 91 ++++++++++++++----- 2 files changed, 72 insertions(+), 47 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCallableStatement.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCallableStatement.java index e8667001e8fb..964ffab74aba 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCallableStatement.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCallableStatement.java @@ -806,19 +806,9 @@ public void setObject(String parameterName, Object value, int targetSqlType) thr setNull(parameterName, targetSqlType); return; } + Class javaType = BigQueryTypeRegistry.toJavaClass(targetSqlType); this.parameterHandler.setParameter( - parameterName, value, value.getClass(), BigQueryStatementParameterType.IN, 0); - StandardSQLTypeName sqlType = this.parameterHandler.getSqlType(parameterName); - if (sqlType != null) { - int javaSqlType = BigQueryTypeRegistry.toJdbcType(sqlType); - if (javaSqlType != targetSqlType) { - throw new BigQueryJdbcSqlFeatureNotSupportedException( - String.format("Unsupported sql type:%s ", targetSqlType)); - } - } else { - throw new BigQueryJdbcSqlFeatureNotSupportedException( - String.format("parameter sql type not supported: %s", sqlType)); - } + parameterName, value, javaType, BigQueryStatementParameterType.IN, 0); } @Override @@ -829,19 +819,9 @@ public void setObject(String parameterName, Object value, int targetSqlType, int setNull(parameterName, targetSqlType); return; } + Class javaType = BigQueryTypeRegistry.toJavaClass(targetSqlType); this.parameterHandler.setParameter( - parameterName, value, value.getClass(), BigQueryStatementParameterType.IN, scaleOrLength); - StandardSQLTypeName sqlType = this.parameterHandler.getSqlType(parameterName); - if (sqlType != null) { - int javaSqlType = BigQueryTypeRegistry.toJdbcType(sqlType); - if (javaSqlType != targetSqlType) { - throw new BigQueryJdbcSqlFeatureNotSupportedException( - String.format("Unsupported sql type:%s ", targetSqlType)); - } - } else { - throw new BigQueryJdbcSqlFeatureNotSupportedException( - String.format("parameter sql type not supported: %s", sqlType)); - } + parameterName, value, javaType, BigQueryStatementParameterType.IN, scaleOrLength); } @Override diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java index 03cb49b9ecc7..289428734567 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java @@ -79,8 +79,12 @@ static TypeDescriptor createBoolDescriptor() { StandardSQLTypeName.BOOL, Arrays.asList(Boolean.class), (val, targetClass, zone) -> { - if (val instanceof Boolean) return val; - if (val instanceof String) return Boolean.parseBoolean((String) val); + if (val instanceof Boolean) { + return val; + } + if (val instanceof String) { + return Boolean.parseBoolean((String) val); + } throw new BigQueryJdbcException("Cannot convert to BOOL: " + val); }); } @@ -106,9 +110,15 @@ static TypeDescriptor createInt64Descriptor() { 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; + if (targetClass == Integer.class) { + return (int) longVal; + } + if (targetClass == Short.class) { + return (short) longVal; + } + if (targetClass == Byte.class) { + return (byte) longVal; + } return longVal; }); } @@ -125,7 +135,9 @@ static TypeDescriptor createFloat64Descriptor() { 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; + if (targetClass == Float.class) { + return (float) doubleVal; + } return doubleVal; }); } @@ -137,9 +149,15 @@ static TypeDescriptor createNumericDescriptor() { 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); + 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); }); } @@ -161,7 +179,9 @@ else if (val instanceof String) sqlDate = BigQueryTemporalUtility.boxDate((String) val, zone); else throw new BigQueryJdbcException("Cannot convert to DATE: " + val); - if (targetClass == LocalDate.class) return sqlDate.toLocalDate(); + if (targetClass == LocalDate.class) { + return sqlDate.toLocalDate(); + } return sqlDate; }); } @@ -183,7 +203,9 @@ else if (val instanceof String) ts = BigQueryTemporalUtility.boxDateTime((String) val, zone); else throw new BigQueryJdbcException("Cannot convert to DATETIME: " + val); - if (targetClass == LocalDateTime.class) return ts.toLocalDateTime(); + if (targetClass == LocalDateTime.class) { + return ts.toLocalDateTime(); + } return ts; }); } @@ -208,7 +230,9 @@ else if (val instanceof ZonedDateTime) else if (val instanceof String) ts = BigQueryTemporalUtility.boxTimestamp((String) val); else throw new BigQueryJdbcException("Cannot convert to TIMESTAMP: " + val); - if (targetClass == Instant.class) return ts.toInstant(); + if (targetClass == Instant.class) { + return ts.toInstant(); + } if (targetClass == OffsetDateTime.class) return ts.toInstant().atOffset(java.time.ZoneOffset.UTC); if (targetClass == ZonedDateTime.class) @@ -255,7 +279,9 @@ static TypeDescriptor createBytesDescriptor() { StandardSQLTypeName.BYTES, Arrays.asList(byte[].class), (val, targetClass, zone) -> { - if (val instanceof byte[]) return val; + if (val instanceof byte[]) { + return val; + } throw new BigQueryJdbcException("Cannot convert to BYTES: " + val); }); } @@ -267,7 +293,9 @@ static TypeDescriptor createArrayDescriptor() { StandardSQLTypeName.ARRAY, Arrays.asList(Array.class), (val, targetClass, zone) -> { - if (val instanceof Array) return val; + if (val instanceof Array) { + return val; + } throw new BigQueryJdbcException("Cannot convert to ARRAY: " + val); }); } @@ -279,7 +307,9 @@ static TypeDescriptor createStructDescriptor() { StandardSQLTypeName.STRUCT, Arrays.asList(Struct.class), (val, targetClass, zone) -> { - if (val instanceof Struct) return val; + if (val instanceof Struct) { + return val; + } throw new BigQueryJdbcException("Cannot convert to STRUCT: " + val); }); } @@ -300,9 +330,15 @@ static TypeDescriptor createBignumericDescriptor() { 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); + 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); }); } @@ -362,10 +398,11 @@ public static StandardSQLTypeName toBigQueryType(Class clazz) { return StandardSQLTypeName.STRING; // Legacy fallback } - /** Returns the default Java target class for a given JDBC type constant. */ /** Returns the JDBC Type constant for a given BigQuery type. */ public static int toJdbcType(StandardSQLTypeName bqType) { - if (bqType == null) return java.sql.Types.OTHER; + if (bqType == null) { + return java.sql.Types.OTHER; + } int ordinal = bqType.ordinal(); if (ordinal >= DESCRIPTORS_BY_ORDINAL.length || DESCRIPTORS_BY_ORDINAL[ordinal] == null) { return java.sql.Types.OTHER; @@ -414,7 +451,9 @@ public static int toJdbcType(StandardSQLTypeName bqType) { /** Returns the exact default Java Class for a given BigQuery type, avoiding lossy mappings. */ public static Class toJavaClass(StandardSQLTypeName bqType) { - if (bqType == null) return String.class; + if (bqType == null) { + return String.class; + } int ordinal = bqType.ordinal(); if (ordinal >= DESCRIPTORS_BY_ORDINAL.length || DESCRIPTORS_BY_ORDINAL[ordinal] == null) { return String.class; @@ -465,7 +504,9 @@ public static T convert(Object input, Class targetClass) throws BigQueryJ */ public static Object convert(Object input, StandardSQLTypeName bqType, ZoneId zoneId) throws BigQueryJdbcException { - if (input == null) return null; + 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); @@ -487,7 +528,9 @@ public static Object convert(Object input, StandardSQLTypeName bqType, ZoneId zo public static T convert( Object input, StandardSQLTypeName bqType, Class targetClass, ZoneId zoneId) throws BigQueryJdbcException { - if (input == null) return null; + 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); @@ -578,6 +621,8 @@ StandardSQLTypeName.RANGE, new ColumnTypeInfo(Types.OTHER, "RANGE", null, null, .put( StandardSQLTypeName.STRUCT, new ColumnTypeInfo(Types.STRUCT, "STRUCT", null, null, null)) + .put( + StandardSQLTypeName.ARRAY, new ColumnTypeInfo(Types.ARRAY, "ARRAY", null, null, null)) .build(); public static ColumnTypeInfo getColumnTypeInfo(StandardSQLTypeName bqType) { From ce7bcd72e924683eb721f2b3b1e758c5ce4f6171 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Mon, 24 Aug 2026 15:27:03 -0400 Subject: [PATCH 15/17] address comments --- .../bigquery/jdbc/BigQueryArrowArray.java | 6 - .../bigquery/jdbc/BigQueryBaseResultSet.java | 30 ++--- .../bigquery/jdbc/BigQueryTypeRegistry.java | 21 ++- .../jdbc/BigQueryTypeRegistryTest.java | 120 ++++++++++++++++++ 4 files changed, 149 insertions(+), 28 deletions(-) create mode 100644 java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistryTest.java diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowArray.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowArray.java index 2a8336768470..81ef40d04d0e 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowArray.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowArray.java @@ -19,10 +19,8 @@ import com.google.cloud.Tuple; import com.google.cloud.bigquery.Field; import com.google.cloud.bigquery.Schema; -import com.google.cloud.bigquery.StandardSQLTypeName; import java.sql.ResultSet; import java.sql.SQLException; -import java.time.LocalDate; import org.apache.arrow.vector.util.JsonStringArrayList; import org.apache.arrow.vector.util.JsonStringHashMap; @@ -102,10 +100,6 @@ public void free() { Object getCoercedValue(int index) throws SQLException { LOG.finestTrace("getCoercedValue"); Object value = this.values.get(index); - if (value instanceof Integer - && schema.getType().getStandardType() == StandardSQLTypeName.DATE) { - value = LocalDate.ofEpochDay(((Integer) value).longValue()); - } return this.arrayOfStruct ? new BigQueryArrowStruct( schema.getSubFields(), (JsonStringHashMap) value, this.LOG.getArrowStructLogger()) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseResultSet.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseResultSet.java index ee52d7504738..70000e749b24 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseResultSet.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseResultSet.java @@ -27,8 +27,6 @@ import com.google.cloud.bigquery.Schema; import com.google.cloud.bigquery.StandardSQLTypeName; import com.google.cloud.bigquery.exception.BigQueryConversionException; -import com.google.cloud.bigquery.exception.BigQueryJdbcCoercionException; -import com.google.cloud.bigquery.exception.BigQueryJdbcCoercionNotFoundException; import com.google.cloud.bigquery.exception.BigQueryJdbcException; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; @@ -324,7 +322,7 @@ public String getString(int columnIndex) throws SQLException { try { Object value = getObject(columnIndex); return BigQueryTypeRegistry.convert(value, String.class); - } catch (BigQueryJdbcCoercionNotFoundException e) { + } catch (BigQueryJdbcException e) { throw createCoercionException(columnIndex, String.class, e); } } @@ -343,7 +341,7 @@ public boolean getBoolean(int columnIndex) throws SQLException { try { Object value = getObject(columnIndex); return BigQueryTypeRegistry.convert(value, Boolean.class); - } catch (BigQueryJdbcCoercionNotFoundException e) { + } catch (BigQueryJdbcException e) { throw createCoercionException(columnIndex, Boolean.class, e); } } @@ -354,7 +352,7 @@ public byte getByte(int columnIndex) throws SQLException { try { Object value = getObject(columnIndex); return BigQueryTypeRegistry.convert(value, Byte.class); - } catch (BigQueryJdbcCoercionNotFoundException | BigQueryJdbcCoercionException e) { + } catch (BigQueryJdbcException e) { throw createCoercionException(columnIndex, Byte.class, e); } } @@ -365,7 +363,7 @@ public short getShort(int columnIndex) throws SQLException { try { Object value = getObject(columnIndex); return BigQueryTypeRegistry.convert(value, Short.class); - } catch (BigQueryJdbcCoercionNotFoundException | BigQueryJdbcCoercionException e) { + } catch (BigQueryJdbcException e) { throw createCoercionException(columnIndex, Short.class, e); } } @@ -376,7 +374,7 @@ public int getInt(int columnIndex) throws SQLException { try { Object value = getObject(columnIndex); return BigQueryTypeRegistry.convert(value, Integer.class); - } catch (BigQueryJdbcCoercionNotFoundException | BigQueryJdbcCoercionException e) { + } catch (BigQueryJdbcException e) { throw createCoercionException(columnIndex, Integer.class, e); } } @@ -387,7 +385,7 @@ public long getLong(int columnIndex) throws SQLException { try { Object value = getObject(columnIndex); return BigQueryTypeRegistry.convert(value, Long.class); - } catch (BigQueryJdbcCoercionNotFoundException | BigQueryJdbcCoercionException e) { + } catch (BigQueryJdbcException e) { throw createCoercionException(columnIndex, Long.class, e); } } @@ -398,7 +396,7 @@ public float getFloat(int columnIndex) throws SQLException { try { Object value = getObject(columnIndex); return BigQueryTypeRegistry.convert(value, Float.class); - } catch (BigQueryJdbcCoercionNotFoundException | BigQueryJdbcCoercionException e) { + } catch (BigQueryJdbcException e) { throw createCoercionException(columnIndex, Float.class, e); } } @@ -409,7 +407,7 @@ public double getDouble(int columnIndex) throws SQLException { try { Object value = getObject(columnIndex); return BigQueryTypeRegistry.convert(value, Double.class); - } catch (BigQueryJdbcCoercionNotFoundException | BigQueryJdbcCoercionException e) { + } catch (BigQueryJdbcException e) { throw createCoercionException(columnIndex, Double.class, e); } } @@ -422,7 +420,7 @@ public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException try { Object value = getObject(columnIndex); return BigQueryTypeRegistry.convert(value, BigDecimal.class); - } catch (BigQueryJdbcCoercionNotFoundException | BigQueryJdbcCoercionException e) { + } catch (BigQueryJdbcException e) { throw createCoercionException(columnIndex, BigDecimal.class, e); } } @@ -433,7 +431,7 @@ public byte[] getBytes(int columnIndex) throws SQLException { try { Object value = getObject(columnIndex); return BigQueryTypeRegistry.convert(value, byte[].class); - } catch (BigQueryJdbcCoercionNotFoundException e) { + } catch (BigQueryJdbcException e) { throw createCoercionException(columnIndex, byte[].class, e); } } @@ -444,7 +442,7 @@ public Date getDate(int columnIndex) throws SQLException { try { Object value = getObject(columnIndex); return BigQueryTypeRegistry.convert(value, java.sql.Date.class); - } catch (BigQueryJdbcCoercionNotFoundException e) { + } catch (BigQueryJdbcException e) { throw createCoercionException(columnIndex, java.sql.Date.class, e); } } @@ -459,7 +457,7 @@ public Time getTime(int columnIndex) throws SQLException { try { Object value = getObject(columnIndex); return BigQueryTypeRegistry.convert(value, java.sql.Time.class); - } catch (BigQueryJdbcCoercionNotFoundException e) { + } catch (BigQueryJdbcException e) { throw createCoercionException(columnIndex, java.sql.Time.class, e); } } @@ -474,7 +472,7 @@ public Timestamp getTimestamp(int columnIndex) throws SQLException { try { Object value = getObject(columnIndex); return BigQueryTypeRegistry.convert(value, java.sql.Timestamp.class); - } catch (BigQueryJdbcCoercionNotFoundException e) { + } catch (BigQueryJdbcException e) { throw createCoercionException(columnIndex, java.sql.Timestamp.class, e); } } @@ -485,7 +483,7 @@ public BigDecimal getBigDecimal(int columnIndex) throws SQLException { try { Object value = getObject(columnIndex); return BigQueryTypeRegistry.convert(value, BigDecimal.class); - } catch (BigQueryJdbcCoercionNotFoundException | BigQueryJdbcCoercionException e) { + } catch (BigQueryJdbcException e) { throw createCoercionException(columnIndex, BigDecimal.class, e); } } diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java index 2832d6534cc5..973c61ee59a8 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java @@ -239,6 +239,8 @@ static TypeDescriptor createDateDescriptor() { return new Date(((java.util.Date) val).getTime()).toLocalDate(); } else if (val instanceof LocalDateTime) { return ((LocalDateTime) val).toLocalDate(); + } else if (val instanceof Number) { + return LocalDate.ofEpochDay(((Number) val).longValue()); } else if (val instanceof String) { return LocalDate.parse((String) val); } else { @@ -261,6 +263,8 @@ static TypeDescriptor createDateDescriptor() { sqlDate = Date.valueOf((LocalDate) val); } else if (val instanceof LocalDateTime) { sqlDate = Date.valueOf(((LocalDateTime) val).toLocalDate()); + } else if (val instanceof Number) { + sqlDate = Date.valueOf(LocalDate.ofEpochDay(((Number) val).longValue())); } else if (val instanceof String) { sqlDate = BigQueryTemporalUtility.boxDate((String) val, zone); } else { @@ -319,7 +323,9 @@ static TypeDescriptor createTimestampDescriptor() { Arrays.asList(Timestamp.class, OffsetDateTime.class, Instant.class, ZonedDateTime.class), (val, targetClass, zone) -> { // Modern fast-path: Bypass intermediate object creation for JSR-310 targets - if (targetClass == Instant.class || targetClass == OffsetDateTime.class || targetClass == ZonedDateTime.class) { + if (targetClass == Instant.class + || targetClass == OffsetDateTime.class + || targetClass == ZonedDateTime.class) { Instant instant; if (val instanceof Instant) { instant = (Instant) val; @@ -361,7 +367,9 @@ static TypeDescriptor createTimestampDescriptor() { } else if (val instanceof LocalDateTime) { ts = Timestamp.from(((LocalDateTime) val).toInstant(ZoneOffset.UTC)); } else if (val instanceof Long) { - ts = Timestamp.from(Instant.EPOCH.plus((Long) val, java.time.temporal.ChronoUnit.MICROS)); + ts = + Timestamp.from( + Instant.EPOCH.plus((Long) val, java.time.temporal.ChronoUnit.MICROS)); } else if (val instanceof String) { ts = BigQueryTemporalUtility.boxTimestamp((String) val); } else { @@ -531,11 +539,12 @@ static TypeDescriptor createIntervalDescriptor() { builder .append(hours) .append(":") - .append(minutes) + .append(String.format("%02d", minutes)) .append(":") - .append(seconds) - .append(".") - .append(microseconds); + .append(String.format("%02d", seconds)); + if (microseconds > 0) { + builder.append(".").append(String.format("%06d", microseconds)); + } return builder.toString().replaceFirst("--", "-"); } return String.valueOf(val); diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistryTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistryTest.java new file mode 100644 index 000000000000..6e76880465de --- /dev/null +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistryTest.java @@ -0,0 +1,120 @@ +/* + * 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 static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.google.cloud.bigquery.exception.BigQueryJdbcException; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Period; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import org.apache.arrow.vector.PeriodDuration; +import org.junit.jupiter.api.Test; + +public class BigQueryTypeRegistryTest { + + @Test + public void testIntervalFormatting() throws Exception { + Period p = Period.of(1, 2, 3); + Duration d = + Duration.ofHours(4).plusMinutes(5).plusSeconds(6).plusNanos(78000); // 78 microseconds + PeriodDuration pd = new PeriodDuration(p, d); + String result = + BigQueryTypeRegistry.convert(pd, StandardSQLTypeName.INTERVAL, String.class, null); + assertThat(result).isEqualTo("1-2 3 4:05:06.000078"); + } + + @Test + public void testIntervalFormattingZeroMicroseconds() throws Exception { + Period p = Period.of(0, 0, 0); + Duration d = Duration.ofHours(1).plusMinutes(0).plusSeconds(0); + PeriodDuration pd = new PeriodDuration(p, d); + String result = + BigQueryTypeRegistry.convert(pd, StandardSQLTypeName.INTERVAL, String.class, null); + assertThat(result).isEqualTo("0-0 0 1:00:00"); + } + + @Test + public void testIntervalFormattingNegativeDuration() throws Exception { + Period p = Period.of(0, 0, 0); + Duration d = Duration.ofHours(-1).minusMinutes(5).minusSeconds(6).minusNanos(78000); + PeriodDuration pd = new PeriodDuration(p, d); + String result = + BigQueryTypeRegistry.convert(pd, StandardSQLTypeName.INTERVAL, String.class, null); + assertThat(result).isEqualTo("0-0 0 -1:05:06.000078"); + } + + @Test + public void testDateFormatting() throws Exception { + LocalDate localDate = LocalDate.of(2026, 8, 24); + assertThat( + BigQueryTypeRegistry.convert(localDate, StandardSQLTypeName.DATE, String.class, null)) + .isEqualTo("2026-08-24"); + assertThat(BigQueryTypeRegistry.convert(localDate, StandardSQLTypeName.DATE, Date.class, null)) + .isEqualTo(Date.valueOf("2026-08-24")); + } + + @Test + public void testTimeFormatting() throws Exception { + LocalTime localTime = LocalTime.of(15, 30, 45, 123456000); // 123.456 ms + assertThat( + BigQueryTypeRegistry.convert(localTime, StandardSQLTypeName.TIME, String.class, null)) + .isEqualTo("15:30:45.123456"); + assertThat(BigQueryTypeRegistry.convert(localTime, StandardSQLTypeName.TIME, Time.class, null)) + .isEqualTo(Time.valueOf("15:30:45")); + } + + @Test + public void testDatetimeFormatting() throws Exception { + LocalDateTime localDateTime = LocalDateTime.of(2026, 8, 24, 15, 30, 45, 123456000); + assertThat( + BigQueryTypeRegistry.convert( + localDateTime, StandardSQLTypeName.DATETIME, String.class, null)) + .isEqualTo("2026-08-24T15:30:45.123456"); + assertThat( + BigQueryTypeRegistry.convert( + localDateTime, StandardSQLTypeName.DATETIME, Timestamp.class, null)) + .isEqualTo(Timestamp.valueOf("2026-08-24 15:30:45.123456")); + } + + @Test + public void testTimestampFormatting() throws Exception { + ZonedDateTime zonedDateTime = + ZonedDateTime.of(2026, 8, 24, 15, 30, 45, 123456000, ZoneId.of("UTC")); + assertThat( + BigQueryTypeRegistry.convert( + zonedDateTime, StandardSQLTypeName.TIMESTAMP, String.class, null)) + .isEqualTo("2026-08-24T15:30:45.123456Z[UTC]"); + } + + @Test + public void testCoercionException() throws Exception { + assertThrows( + BigQueryJdbcException.class, + () -> + BigQueryTypeRegistry.convert( + "bad_number", StandardSQLTypeName.INT64, Integer.class, null)); + } +} From f78d7042bf325dd26280ecefad5b0435f088292d Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Mon, 24 Aug 2026 15:35:30 -0400 Subject: [PATCH 16/17] add tests --- .../bigquery/jdbc/BigQueryTypeRegistryTest.java | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistryTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistryTest.java index 6e76880465de..565bcaa64c46 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistryTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistryTest.java @@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import com.google.cloud.bigquery.StandardSQLTypeName; import com.google.cloud.bigquery.exception.BigQueryJdbcException; import java.sql.Date; import java.sql.Time; @@ -69,9 +70,6 @@ public void testIntervalFormattingNegativeDuration() throws Exception { @Test public void testDateFormatting() throws Exception { LocalDate localDate = LocalDate.of(2026, 8, 24); - assertThat( - BigQueryTypeRegistry.convert(localDate, StandardSQLTypeName.DATE, String.class, null)) - .isEqualTo("2026-08-24"); assertThat(BigQueryTypeRegistry.convert(localDate, StandardSQLTypeName.DATE, Date.class, null)) .isEqualTo(Date.valueOf("2026-08-24")); } @@ -79,9 +77,6 @@ public void testDateFormatting() throws Exception { @Test public void testTimeFormatting() throws Exception { LocalTime localTime = LocalTime.of(15, 30, 45, 123456000); // 123.456 ms - assertThat( - BigQueryTypeRegistry.convert(localTime, StandardSQLTypeName.TIME, String.class, null)) - .isEqualTo("15:30:45.123456"); assertThat(BigQueryTypeRegistry.convert(localTime, StandardSQLTypeName.TIME, Time.class, null)) .isEqualTo(Time.valueOf("15:30:45")); } @@ -89,10 +84,6 @@ public void testTimeFormatting() throws Exception { @Test public void testDatetimeFormatting() throws Exception { LocalDateTime localDateTime = LocalDateTime.of(2026, 8, 24, 15, 30, 45, 123456000); - assertThat( - BigQueryTypeRegistry.convert( - localDateTime, StandardSQLTypeName.DATETIME, String.class, null)) - .isEqualTo("2026-08-24T15:30:45.123456"); assertThat( BigQueryTypeRegistry.convert( localDateTime, StandardSQLTypeName.DATETIME, Timestamp.class, null)) @@ -105,8 +96,8 @@ public void testTimestampFormatting() throws Exception { ZonedDateTime.of(2026, 8, 24, 15, 30, 45, 123456000, ZoneId.of("UTC")); assertThat( BigQueryTypeRegistry.convert( - zonedDateTime, StandardSQLTypeName.TIMESTAMP, String.class, null)) - .isEqualTo("2026-08-24T15:30:45.123456Z[UTC]"); + zonedDateTime, StandardSQLTypeName.TIMESTAMP, Timestamp.class, null)) + .isEqualTo(Timestamp.from(zonedDateTime.toInstant())); } @Test From 4744d13476976f80da34e093b51249f599714e26 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Tue, 25 Aug 2026 09:47:02 -0400 Subject: [PATCH 17/17] fix number to date conversion --- .../bigquery/jdbc/BigQueryTypeRegistry.java | 30 ++++++++++++++----- .../jdbc/BigQueryTypeRegistryTest.java | 6 ++-- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java index b9ef1f76a429..e3f993c7b4cf 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistry.java @@ -239,8 +239,8 @@ static TypeDescriptor createDateDescriptor() { return new Date(((java.util.Date) val).getTime()).toLocalDate(); } else if (val instanceof LocalDateTime) { return ((LocalDateTime) val).toLocalDate(); - } else if (val instanceof Number) { - return LocalDate.ofEpochDay(((Number) val).longValue()); + } else if (val instanceof Integer) { + return LocalDate.ofEpochDay(((Integer) val).longValue()); } else if (val instanceof String) { return LocalDate.parse((String) val); } else { @@ -263,8 +263,8 @@ static TypeDescriptor createDateDescriptor() { sqlDate = Date.valueOf((LocalDate) val); } else if (val instanceof LocalDateTime) { sqlDate = Date.valueOf(((LocalDateTime) val).toLocalDate()); - } else if (val instanceof Number) { - sqlDate = Date.valueOf(LocalDate.ofEpochDay(((Number) val).longValue())); + } else if (val instanceof Integer) { + sqlDate = Date.valueOf(LocalDate.ofEpochDay(((Integer) val).longValue())); } else if (val instanceof String) { sqlDate = BigQueryTemporalUtility.boxDate((String) val, zone); } else { @@ -545,11 +545,25 @@ static TypeDescriptor createIntervalDescriptor() { builder .append(hours) .append(":") - .append(String.format("%02d", minutes)) + .append(minutes) .append(":") - .append(String.format("%02d", seconds)); - if (microseconds > 0) { - builder.append(".").append(String.format("%06d", microseconds)); + .append(seconds) + .append("."); + + if (microseconds == 0) { + builder.append("0"); + } else { + // Left pad to 6 digits to preserve mathematical correctness + // e.g. 50 microseconds -> "000050" (so it prints .000050, not .50) + String microsStr = String.format("%06d", microseconds); + + // Strip trailing zeroes to cleanly format the fraction + // e.g. 1000 microseconds -> "001000" -> "001" (prints .001) + int lastNonZero = microsStr.length() - 1; + while (lastNonZero >= 0 && microsStr.charAt(lastNonZero) == '0') { + lastNonZero--; + } + builder.append(microsStr.substring(0, lastNonZero + 1)); } return builder.toString().replaceFirst("--", "-"); } diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistryTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistryTest.java index 565bcaa64c46..060882c750a5 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistryTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTypeRegistryTest.java @@ -44,7 +44,7 @@ public void testIntervalFormatting() throws Exception { PeriodDuration pd = new PeriodDuration(p, d); String result = BigQueryTypeRegistry.convert(pd, StandardSQLTypeName.INTERVAL, String.class, null); - assertThat(result).isEqualTo("1-2 3 4:05:06.000078"); + assertThat(result).isEqualTo("1-2 3 4:5:6.000078"); } @Test @@ -54,7 +54,7 @@ public void testIntervalFormattingZeroMicroseconds() throws Exception { PeriodDuration pd = new PeriodDuration(p, d); String result = BigQueryTypeRegistry.convert(pd, StandardSQLTypeName.INTERVAL, String.class, null); - assertThat(result).isEqualTo("0-0 0 1:00:00"); + assertThat(result).isEqualTo("0-0 0 1:0:0.0"); } @Test @@ -64,7 +64,7 @@ public void testIntervalFormattingNegativeDuration() throws Exception { PeriodDuration pd = new PeriodDuration(p, d); String result = BigQueryTypeRegistry.convert(pd, StandardSQLTypeName.INTERVAL, String.class, null); - assertThat(result).isEqualTo("0-0 0 -1:05:06.000078"); + assertThat(result).isEqualTo("0-0 0 -1:5:6.000078"); } @Test