From c034c01f1bdaecf0b589df4b5537654794040e78 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Fri, 17 Jul 2026 16:05:09 -0400 Subject: [PATCH 1/5] feat(bigquery-jdbc): implement BigQueryParameterMetaData and dynamic type mappings --- .../jdbc/BigQueryJdbcTypeMappings.java | 16 +- .../jdbc/BigQueryParameterMetaData.java | 175 ++++++++++++++++++ 2 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryParameterMetaData.java diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcTypeMappings.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcTypeMappings.java index 648cf8fd4673..19b53d6e2ae7 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcTypeMappings.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcTypeMappings.java @@ -28,6 +28,12 @@ import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; import java.util.AbstractMap.SimpleEntry; import java.util.Map; @@ -117,11 +123,15 @@ static StandardSQLTypeName classToType(Class type) return StandardSQLTypeName.FLOAT64; } else if (BigDecimal.class.isAssignableFrom(type)) { return StandardSQLTypeName.NUMERIC; - } else if (Date.class.isAssignableFrom(type)) { + } else if (Date.class.isAssignableFrom(type) || LocalDate.class.isAssignableFrom(type)) { return StandardSQLTypeName.DATE; - } else if (Timestamp.class.isAssignableFrom(type)) { + } else if (Timestamp.class.isAssignableFrom(type) + || LocalDateTime.class.isAssignableFrom(type) + || OffsetDateTime.class.isAssignableFrom(type) + || Instant.class.isAssignableFrom(type) + || ZonedDateTime.class.isAssignableFrom(type)) { return StandardSQLTypeName.TIMESTAMP; - } else if (Time.class.isAssignableFrom(type)) { + } else if (Time.class.isAssignableFrom(type) || LocalTime.class.isAssignableFrom(type)) { return StandardSQLTypeName.TIME; } else if (JsonObject.class.isAssignableFrom(type)) { return StandardSQLTypeName.JSON; 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 new file mode 100644 index 000000000000..3c366765ffcf --- /dev/null +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryParameterMetaData.java @@ -0,0 +1,175 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.bigquery.jdbc; + +import com.google.cloud.bigquery.StandardSQLTypeName; +import com.google.cloud.bigquery.exception.BigQueryJdbcException; +import java.sql.ParameterMetaData; +import java.sql.SQLException; +import java.sql.Types; + +/** ParameterMetaData implementation for BigQuery JDBC PreparedStatement parameters. */ +class BigQueryParameterMetaData implements ParameterMetaData { + private final int parameterCount; + private final BigQueryParameterHandler parameterHandler; + + BigQueryParameterMetaData(int parameterCount, BigQueryParameterHandler parameterHandler) { + this.parameterCount = parameterCount; + this.parameterHandler = parameterHandler; + } + + private void checkValidIndex(int param) throws SQLException { + if (param < 1 || param > this.parameterCount) { + throw new BigQueryJdbcException("Invalid parameter index: " + param); + } + } + + @Override + public int getParameterCount() { + return this.parameterCount; + } + + @Override + public int isNullable(int param) throws SQLException { + checkValidIndex(param); + return parameterNullable; + } + + @Override + public boolean isSigned(int param) throws SQLException { + int type = getParameterType(param); + return type == Types.SMALLINT + || type == Types.TINYINT + || type == Types.INTEGER + || type == Types.BIGINT + || type == Types.FLOAT + || type == Types.DOUBLE + || type == Types.DECIMAL + || type == Types.NUMERIC; + } + + @Override + public int getPrecision(int param) throws SQLException { + checkValidIndex(param); + StandardSQLTypeName sqlType = getStandardSQLTypeName(param); + if (sqlType != null) { + BigQueryJdbcTypeMappings.ColumnTypeInfo typeInfo = + BigQueryJdbcTypeMappings.STANDARD_TYPE_INFO.get(sqlType); + if (typeInfo != null && typeInfo.columnSize != null) { + return typeInfo.columnSize; + } + } + return 0; + } + + @Override + public int getScale(int param) throws SQLException { + checkValidIndex(param); + StandardSQLTypeName sqlType = getStandardSQLTypeName(param); + if (sqlType != null) { + BigQueryJdbcTypeMappings.ColumnTypeInfo typeInfo = + BigQueryJdbcTypeMappings.STANDARD_TYPE_INFO.get(sqlType); + if (typeInfo != null && typeInfo.decimalDigits != null) { + return typeInfo.decimalDigits; + } + } + return 0; + } + + @Override + public int getParameterType(int param) throws SQLException { + checkValidIndex(param); + StandardSQLTypeName sqlType = getStandardSQLTypeName(param); + if (sqlType != null) { + Integer jdbcType = BigQueryJdbcTypeMappings.standardSQLToJavaSqlTypesMapping.get(sqlType); + if (jdbcType != null) { + return jdbcType; + } + } + return Types.OTHER; + } + + @Override + public String getParameterTypeName(int param) throws SQLException { + checkValidIndex(param); + StandardSQLTypeName sqlType = getStandardSQLTypeName(param); + return sqlType != null ? sqlType.name() : "UNKNOWN"; + } + + @Override + public String getParameterClassName(int param) throws SQLException { + checkValidIndex(param); + StandardSQLTypeName sqlType = getStandardSQLTypeName(param); + if (sqlType != null) { + Class clazz = BigQueryJdbcTypeMappings.standardSQLToJavaTypeMapping.get(sqlType); + if (clazz != null) { + return clazz.getName(); + } + } + Class boundType = this.parameterHandler.getType(param); + if (boundType != null) { + return boundType.getName(); + } + return Object.class.getName(); + } + + @Override + public int getParameterMode(int param) throws SQLException { + checkValidIndex(param); + if (this.parameterHandler != null) { + BigQueryParameterHandler.BigQueryStatementParameterType paramType = + this.parameterHandler.getParameterType(param); + if (paramType == BigQueryParameterHandler.BigQueryStatementParameterType.OUT) { + return parameterModeOut; + } else if (paramType == BigQueryParameterHandler.BigQueryStatementParameterType.INOUT) { + return parameterModeInOut; + } + } + return parameterModeIn; + } + + private StandardSQLTypeName getStandardSQLTypeName(int param) { + if (this.parameterHandler != null) { + StandardSQLTypeName sqlType = this.parameterHandler.getSqlType(param); + if (sqlType != null) { + return sqlType; + } + Class javaType = this.parameterHandler.getType(param); + if (javaType != null) { + try { + return BigQueryJdbcTypeMappings.classToType(javaType); + } catch (SQLException ignored) { + // fall back to default + } + } + } + return null; + } + + @Override + public T unwrap(Class iface) throws SQLException { + if (iface.isInstance(this)) { + return iface.cast(this); + } + throw new BigQueryJdbcException("Cannot unwrap to " + iface.getName()); + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return iface != null && iface.isInstance(this); + } +} From c215bf9e11be84a80b93e40255a410b378761ffa Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Fri, 17 Jul 2026 16:05:38 -0400 Subject: [PATCH 2/5] fix(bigquery-jdbc): refine temporal timezone coercion and PreparedStatement parameter setters --- .../jdbc/BigQueryPreparedStatement.java | 108 ++++++++++++--- .../jdbc/BigQueryTypeCoercionUtility.java | 95 +++++++++++++ .../BigQueryPreparedStatementSettersTest.java | 131 ++++++++++++++++++ ...dValueTypeBigQueryCoercionUtilityTest.java | 33 +++++ .../bigquery/jdbc/it/ITBigQueryJDBCTest.java | 27 +++- 5 files changed, 375 insertions(+), 19 deletions(-) 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 12b450d044b1..b512ea809467 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 @@ -57,6 +57,12 @@ import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -70,7 +76,7 @@ class BigQueryPreparedStatement extends BigQueryStatement implements PreparedSta protected int parameterCount = 0; protected String currentQuery; private Queue> batchParameters = new LinkedList<>(); - private Schema insertSchema = null; + Schema insertSchema = null; private TableName insertTableName = null; BigQueryPreparedStatement(BigQueryConnection connection, String query) { @@ -207,20 +213,20 @@ public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException { } @Override - public void setAsciiStream(int parameterIndex, InputStream x, int length) { - // TODO :NOT IMPLEMENTED + public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setAsciiStream is not supported."); } @Override @Deprecated @SuppressWarnings("deprecation") - public void setUnicodeStream(int parameterIndex, InputStream x, int length) { - // TODO :NOT IMPLEMENTED + public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setUnicodeStream is not supported."); } @Override - public void setBinaryStream(int parameterIndex, InputStream x, int length) { - // TODO :NOT IMPLEMENTED + public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setBinaryStream is not supported."); } @Override @@ -230,6 +236,30 @@ public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQ setNull(parameterIndex, targetSqlType); return; } + if (x instanceof LocalDate) { + setDate(parameterIndex, Date.valueOf((LocalDate) x)); + return; + } + if (x instanceof LocalTime) { + setTime(parameterIndex, Time.valueOf((LocalTime) x)); + return; + } + if (x instanceof LocalDateTime) { + setTimestamp(parameterIndex, Timestamp.valueOf((LocalDateTime) x)); + return; + } + if (x instanceof OffsetDateTime) { + setTimestamp(parameterIndex, Timestamp.from(((OffsetDateTime) x).toInstant())); + return; + } + if (x instanceof Instant) { + setTimestamp(parameterIndex, Timestamp.from((Instant) x)); + return; + } + if (x instanceof ZonedDateTime) { + setTimestamp(parameterIndex, Timestamp.from(((ZonedDateTime) x).toInstant())); + return; + } Class javaType = BigQueryJdbcTypeMappings.getJavaType(targetSqlType); this.parameterHandler.setParameter(parameterIndex, x, javaType); } @@ -241,6 +271,30 @@ public void setObject(int parameterIndex, Object x) throws SQLException { setNull(parameterIndex, Types.NULL); return; } + if (x instanceof LocalDate) { + setDate(parameterIndex, Date.valueOf((LocalDate) x)); + return; + } + if (x instanceof LocalTime) { + setTime(parameterIndex, Time.valueOf((LocalTime) x)); + return; + } + if (x instanceof LocalDateTime) { + setTimestamp(parameterIndex, Timestamp.valueOf((LocalDateTime) x)); + return; + } + if (x instanceof OffsetDateTime) { + setTimestamp(parameterIndex, Timestamp.from(((OffsetDateTime) x).toInstant())); + return; + } + if (x instanceof Instant) { + setTimestamp(parameterIndex, Timestamp.from((Instant) x)); + return; + } + if (x instanceof ZonedDateTime) { + setTimestamp(parameterIndex, Timestamp.from(((ZonedDateTime) x).toInstant())); + return; + } this.parameterHandler.setParameter(parameterIndex, x, x.getClass()); } @@ -470,24 +524,42 @@ public void setArray(int parameterIndex, Array x) throws SQLException { } @Override - public ResultSetMetaData getMetaData() { - // TODO(neenu) :IMPLEMENT metadata + public ResultSetMetaData getMetaData() throws SQLException { + checkClosed(); + if (this.insertSchema != null) { + return BigQueryResultSetMetadata.of(this.insertSchema.getFields(), this); + } return null; } @Override - public void setDate(int parameterIndex, Date x, Calendar cal) { - // TODO :NOT IMPLEMENTED + public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { + checkClosed(); + if (x == null) { + setNull(parameterIndex, Types.DATE); + return; + } + setDate(parameterIndex, BigQueryTypeCoercionUtility.convertDateWithCalendar(x, cal)); } @Override - public void setTime(int parameterIndex, Time x, Calendar cal) { - // TODO :NOT IMPLEMENTED + public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { + checkClosed(); + if (x == null) { + setNull(parameterIndex, Types.TIME); + return; + } + setTime(parameterIndex, BigQueryTypeCoercionUtility.convertTimeWithCalendar(x, cal)); } @Override - public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) { - // TODO :NOT IMPLEMENTED + public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException { + checkClosed(); + if (x == null) { + setNull(parameterIndex, Types.TIMESTAMP); + return; + } + setTimestamp(parameterIndex, BigQueryTypeCoercionUtility.convertTimestampWithCalendar(x, cal)); } @Override @@ -501,9 +573,9 @@ public void setURL(int parameterIndex, URL x) throws SQLException { } @Override - public ParameterMetaData getParameterMetaData() { - // TODO(neenu) :IMPLEMENT - return null; + public ParameterMetaData getParameterMetaData() throws SQLException { + checkClosed(); + return new BigQueryParameterMetaData(this.parameterCount, this.parameterHandler); } @Override diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercionUtility.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercionUtility.java index 52d03db703ff..931fa0afc644 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercionUtility.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercionUtility.java @@ -31,9 +31,12 @@ import java.time.LocalTime; 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.time.temporal.ChronoUnit; +import java.util.Calendar; import java.util.concurrent.TimeUnit; import org.apache.arrow.vector.PeriodDuration; import org.apache.arrow.vector.util.Text; @@ -43,6 +46,98 @@ class BigQueryTypeCoercionUtility { private static final BigQueryJdbcCustomLogger LOG = new BigQueryJdbcCustomLogger(BigQueryTypeCoercionUtility.class.getName()); + /** Returns a defensively cloned Calendar instance or a new default Calendar if input is null. */ + static Calendar getSafeCalendar(Calendar cal) { + if (cal == null) { + return Calendar.getInstance(); + } + Object cloned = cal.clone(); + if (cloned instanceof Calendar) { + return (Calendar) cloned; + } + Calendar safeCal = Calendar.getInstance(); + if (cal.getTimeZone() != null) { + safeCal.setTimeZone(cal.getTimeZone()); + } + return safeCal; + } + + /** + * Converts a java.sql.Date by shifting its wall-clock year, month, and day fields into the target + * Calendar's timezone per JDBC specification. + */ + static Date convertDateWithCalendar(Date date, Calendar cal) { + if (date == null || cal == null) { + return date; + } + ZoneId systemZone = ZoneId.systemDefault(); + ZoneId targetZone = cal.getTimeZone().toZoneId(); + if (systemZone.equals(targetZone)) { + return date; + } + LocalDate localDate = date.toLocalDate(); + ZonedDateTime zdt = localDate.atStartOfDay(targetZone); + return new Date(zdt.toInstant().toEpochMilli()); + } + + static Date convertDateToCalendar(Date date, Calendar cal) { + if (date == null || cal == null) { + return date; + } + ZoneId systemZone = ZoneId.systemDefault(); + ZoneId targetZone = cal.getTimeZone().toZoneId(); + if (systemZone.equals(targetZone)) { + return date; + } + LocalDate localDate = Instant.ofEpochMilli(date.getTime()).atZone(targetZone).toLocalDate(); + ZonedDateTime zdt = localDate.atStartOfDay(systemZone); + return new Date(zdt.toInstant().toEpochMilli()); + } + + /** + * Converts a java.sql.Time by shifting its wall-clock hour, minute, second, and millisecond + * fields into the target Calendar's timezone per JDBC specification. + */ + static Time convertTimeWithCalendar(Time time, Calendar cal) { + if (time == null || cal == null) { + return time; + } + ZoneId systemZone = ZoneId.systemDefault(); + ZoneId targetZone = cal.getTimeZone().toZoneId(); + if (systemZone.equals(targetZone)) { + return time; + } + Calendar defaultCal = Calendar.getInstance(); + defaultCal.setTime(time); + + Calendar targetCal = getSafeCalendar(cal); + targetCal.set(Calendar.HOUR_OF_DAY, defaultCal.get(Calendar.HOUR_OF_DAY)); + targetCal.set(Calendar.MINUTE, defaultCal.get(Calendar.MINUTE)); + targetCal.set(Calendar.SECOND, defaultCal.get(Calendar.SECOND)); + targetCal.set(Calendar.MILLISECOND, defaultCal.get(Calendar.MILLISECOND)); + return new Time(targetCal.getTimeInMillis()); + } + + /** + * Converts a java.sql.Timestamp by shifting its wall-clock fields into the target Calendar's + * timezone per JDBC specification while preserving nanosecond precision. + */ + static Timestamp convertTimestampWithCalendar(Timestamp timestamp, Calendar cal) { + if (timestamp == null || cal == null) { + return timestamp; + } + ZoneId systemZone = ZoneId.systemDefault(); + ZoneId targetZone = cal.getTimeZone().toZoneId(); + if (systemZone.equals(targetZone)) { + return timestamp; + } + LocalDateTime ldt = timestamp.toLocalDateTime(); + ZonedDateTime zdt = ldt.atZone(targetZone); + Timestamp adjustedTimestamp = Timestamp.from(zdt.toInstant()); + adjustedTimestamp.setNanos(timestamp.getNanos()); + return adjustedTimestamp; + } + static BigQueryTypeCoercer INSTANCE; static { diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryPreparedStatementSettersTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryPreparedStatementSettersTest.java index f9b07efabd90..60cc9b8d4a6d 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryPreparedStatementSettersTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryPreparedStatementSettersTest.java @@ -18,13 +18,28 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; +import com.google.cloud.bigquery.Field; +import com.google.cloud.bigquery.Schema; import com.google.cloud.bigquery.StandardSQLTypeName; import java.sql.Array; +import java.sql.Date; +import java.sql.ParameterMetaData; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; import java.sql.Types; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Calendar; +import java.util.TimeZone; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -138,4 +153,120 @@ public void testSetArray() throws Exception { assertEquals(Array.class, preparedStatement.parameterHandler.getType(1)); assertEquals(StandardSQLTypeName.ARRAY, preparedStatement.parameterHandler.getSqlType(1)); } + + @Test + public void testGetParameterMetaData() throws Exception { + preparedStatement.setString(1, "test"); + preparedStatement.setInt(2, 42); + + ParameterMetaData metaData = preparedStatement.getParameterMetaData(); + assertEquals(5, metaData.getParameterCount()); + assertEquals(Types.NVARCHAR, metaData.getParameterType(1)); + assertEquals("STRING", metaData.getParameterTypeName(1)); + assertEquals(String.class.getName(), metaData.getParameterClassName(1)); + + assertEquals(Types.BIGINT, metaData.getParameterType(2)); + assertEquals("INT64", metaData.getParameterTypeName(2)); + assertEquals(Long.class.getName(), metaData.getParameterClassName(2)); + + assertEquals(ParameterMetaData.parameterNullable, metaData.isNullable(1)); + assertEquals(ParameterMetaData.parameterModeIn, metaData.getParameterMode(1)); + + assertThrows(SQLException.class, () -> metaData.getParameterType(0)); + assertThrows(SQLException.class, () -> metaData.getParameterType(6)); + } + + @Test + public void testGetParameterModeWithInOutParameters() throws Exception { + preparedStatement.parameterHandler.setParameter( + 1, "inParam", String.class, BigQueryParameterHandler.BigQueryStatementParameterType.IN, 0); + preparedStatement.parameterHandler.setParameter( + 2, null, Integer.class, BigQueryParameterHandler.BigQueryStatementParameterType.OUT, 0); + preparedStatement.parameterHandler.setParameter( + 3, + "inoutParam", + String.class, + BigQueryParameterHandler.BigQueryStatementParameterType.INOUT, + 0); + + ParameterMetaData metaData = preparedStatement.getParameterMetaData(); + assertEquals(ParameterMetaData.parameterModeIn, metaData.getParameterMode(1)); + assertEquals(ParameterMetaData.parameterModeOut, metaData.getParameterMode(2)); + assertEquals(ParameterMetaData.parameterModeInOut, metaData.getParameterMode(3)); + } + + @Test + public void testSetDateWithCalendar() throws Exception { + Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + cal.setTimeInMillis(1000000L); + long originalMillis = cal.getTimeInMillis(); + Date date = new Date(1700000000000L); // 2023-11-14 + + preparedStatement.setDate(1, date, cal); + assertEquals(String.class, preparedStatement.parameterHandler.getType(1)); + assertEquals(originalMillis, cal.getTimeInMillis()); + } + + @Test + public void testSetTimeWithCalendar() throws Exception { + Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + cal.setTimeInMillis(1000000L); + long originalMillis = cal.getTimeInMillis(); + Time time = new Time(43200000L); // 12:00:00 + + preparedStatement.setTime(1, time, cal); + assertEquals(String.class, preparedStatement.parameterHandler.getType(1)); + assertEquals(originalMillis, cal.getTimeInMillis()); + } + + @Test + public void testSetTimestampWithCalendar() throws Exception { + Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + cal.setTimeInMillis(1000000L); + long originalMillis = cal.getTimeInMillis(); + Timestamp ts = new Timestamp(1700000000000L); + + preparedStatement.setTimestamp(1, ts, cal); + assertEquals(String.class, preparedStatement.parameterHandler.getType(1)); + assertEquals(originalMillis, cal.getTimeInMillis()); + } + + @Test + public void testGetMetaData() throws Exception { + // Before execution/insertSchema initialization, getMetaData() returns null + assertNull(preparedStatement.getMetaData()); + + // When insertSchema is present, getMetaData() returns ResultSetMetaData + preparedStatement.insertSchema = + Schema.of( + Field.of("col1", StandardSQLTypeName.STRING), + Field.of("col2", StandardSQLTypeName.INT64)); + + ResultSetMetaData metaData = preparedStatement.getMetaData(); + assertNotNull(metaData); + assertEquals(2, metaData.getColumnCount()); + assertEquals("col1", metaData.getColumnName(1)); + assertEquals(Types.NVARCHAR, metaData.getColumnType(1)); + assertEquals("col2", metaData.getColumnName(2)); + assertEquals(Types.BIGINT, metaData.getColumnType(2)); + } + + @Test + public void testSetObjectWithJavaTime() throws Exception { + LocalDate localDate = LocalDate.of(2025, 12, 3); + preparedStatement.setObject(1, localDate); + assertEquals(String.class, preparedStatement.parameterHandler.getType(1)); + + LocalTime localTime = LocalTime.of(12, 30, 0); + preparedStatement.setObject(2, localTime); + assertEquals(String.class, preparedStatement.parameterHandler.getType(2)); + + LocalDateTime localDateTime = LocalDateTime.of(2025, 12, 3, 12, 30, 0); + preparedStatement.setObject(3, localDateTime); + assertEquals(String.class, preparedStatement.parameterHandler.getType(3)); + + Instant instant = Instant.now(); + preparedStatement.setObject(4, instant); + assertEquals(String.class, preparedStatement.parameterHandler.getType(4)); + } } diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/FieldValueTypeBigQueryCoercionUtilityTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/FieldValueTypeBigQueryCoercionUtilityTest.java index eca7a51f6e8f..7b24e389f853 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/FieldValueTypeBigQueryCoercionUtilityTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/FieldValueTypeBigQueryCoercionUtilityTest.java @@ -39,6 +39,7 @@ import java.time.LocalDate; import java.time.LocalTime; import java.time.temporal.ChronoUnit; +import java.util.Calendar; import java.util.TimeZone; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -393,4 +394,36 @@ public void fieldValueToObjectWhenNull() { public void fieldValueToObjectWhenInnerValueIsNull() { assertThat(INSTANCE.coerceTo(Object.class, NULL_VALUE)).isNull(); } + + @Test + public void testCalendarConversions() { + assertThat(BigQueryTypeCoercionUtility.convertDateWithCalendar(null, null)).isNull(); + assertThat(BigQueryTypeCoercionUtility.convertTimeWithCalendar(null, null)).isNull(); + assertThat(BigQueryTypeCoercionUtility.convertTimestampWithCalendar(null, null)).isNull(); + + Date rawDate = Date.valueOf("2026-07-17"); + Time rawTime = Time.valueOf("14:30:00"); + Timestamp rawTimestamp = Timestamp.valueOf("2026-07-17 14:30:00.123456789"); + + // Null calendar returns input unchanged + assertThat(BigQueryTypeCoercionUtility.convertDateWithCalendar(rawDate, null)) + .isEqualTo(rawDate); + assertThat(BigQueryTypeCoercionUtility.convertTimeWithCalendar(rawTime, null)) + .isEqualTo(rawTime); + assertThat(BigQueryTypeCoercionUtility.convertTimestampWithCalendar(rawTimestamp, null)) + .isEqualTo(rawTimestamp); + + // UTC Calendar shifts wall-clock components into target timezone + Calendar utcCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + Date utcConvertedDate = BigQueryTypeCoercionUtility.convertDateWithCalendar(rawDate, utcCal); + assertThat(utcConvertedDate).isNotNull(); + + Time utcConvertedTime = BigQueryTypeCoercionUtility.convertTimeWithCalendar(rawTime, utcCal); + assertThat(utcConvertedTime).isNotNull(); + + Timestamp utcConvertedTimestamp = + BigQueryTypeCoercionUtility.convertTimestampWithCalendar(rawTimestamp, utcCal); + assertThat(utcConvertedTimestamp).isNotNull(); + assertThat(utcConvertedTimestamp.getNanos()).isEqualTo(123456789); + } } diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java index 3fd1489c1afe..9df2f8b624e4 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITBigQueryJDBCTest.java @@ -44,6 +44,7 @@ import java.sql.Date; import java.sql.Driver; import java.sql.DriverManager; +import java.sql.ParameterMetaData; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; @@ -53,8 +54,10 @@ import java.sql.Timestamp; import java.sql.Types; import java.time.LocalTime; +import java.util.Calendar; import java.util.Properties; import java.util.Random; +import java.util.TimeZone; import java.util.function.BiFunction; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; @@ -1515,8 +1518,19 @@ public void testPreparedStatementSmallSelect() throws SQLException { PreparedStatement preparedStatement = bigQueryConnection.prepareStatement(query); preparedStatement.setString(1, "hamlet"); + ParameterMetaData paramMetaData = preparedStatement.getParameterMetaData(); + assertNotNull(paramMetaData); + assertEquals(1, paramMetaData.getParameterCount()); + assertEquals(Types.NVARCHAR, paramMetaData.getParameterType(1)); + assertEquals("STRING", paramMetaData.getParameterTypeName(1)); + ResultSet jsonResultSet = preparedStatement.executeQuery(); + ResultSetMetaData resultSetMetaData = preparedStatement.getMetaData(); + if (resultSetMetaData != null) { + assertTrue(resultSetMetaData.getColumnCount() > 0); + } + int rowCount = resultSetRowCount(jsonResultSet); assertEquals(1000, rowCount); assertTrue(jsonResultSet.getClass().getName().contains("BigQueryJsonResultSet")); @@ -1643,11 +1657,22 @@ public void testPreparedStatementDateTimeValues() throws SQLException { int insertStatus = insertPs.executeUpdate(); assertEquals(1, insertStatus); + // Test Calendar overloads (setDate, setTime, setTimestamp with Calendar) + Calendar utcCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + insertPs.setString(1, "refrigerator"); + insertPs.setInt(2, 2); + insertPs.setTimestamp(3, new Timestamp(System.currentTimeMillis()), utcCal); + insertPs.setTime(4, Time.valueOf(LocalTime.NOON), utcCal); + insertPs.setDate(5, Date.valueOf("2025-12-03"), utcCal); + + int insertStatus2 = insertPs.executeUpdate(); + assertEquals(1, insertStatus2); + ResultSet rs = bigQueryStatement.executeQuery( String.format("SELECT COUNT(*) AS row_count\n" + "FROM %s.%s", DATASET, TABLE_NAME1)); rs.next(); - assertEquals(1, rs.getInt(1)); + assertEquals(2, rs.getInt(1)); String dropQuery = String.format("DROP TABLE %s.%s", DATASET, TABLE_NAME1); int dropStatus = bigQueryStatement.executeUpdate(dropQuery); From fcb001789668e787a3672092d4930c4a83318f06 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Fri, 17 Jul 2026 16:17:17 -0400 Subject: [PATCH 3/5] refactor(bigquery-jdbc): extract setTemporalObject helper in BigQueryPreparedStatement --- .../jdbc/BigQueryPreparedStatement.java | 44 +++++++------------ 1 file changed, 15 insertions(+), 29 deletions(-) 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 b512ea809467..bdae55fd2b0c 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 @@ -236,28 +236,7 @@ public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQ setNull(parameterIndex, targetSqlType); return; } - if (x instanceof LocalDate) { - setDate(parameterIndex, Date.valueOf((LocalDate) x)); - return; - } - if (x instanceof LocalTime) { - setTime(parameterIndex, Time.valueOf((LocalTime) x)); - return; - } - if (x instanceof LocalDateTime) { - setTimestamp(parameterIndex, Timestamp.valueOf((LocalDateTime) x)); - return; - } - if (x instanceof OffsetDateTime) { - setTimestamp(parameterIndex, Timestamp.from(((OffsetDateTime) x).toInstant())); - return; - } - if (x instanceof Instant) { - setTimestamp(parameterIndex, Timestamp.from((Instant) x)); - return; - } - if (x instanceof ZonedDateTime) { - setTimestamp(parameterIndex, Timestamp.from(((ZonedDateTime) x).toInstant())); + if (setTemporalObject(parameterIndex, x)) { return; } Class javaType = BigQueryJdbcTypeMappings.getJavaType(targetSqlType); @@ -271,31 +250,38 @@ public void setObject(int parameterIndex, Object x) throws SQLException { setNull(parameterIndex, Types.NULL); return; } + if (setTemporalObject(parameterIndex, x)) { + return; + } + this.parameterHandler.setParameter(parameterIndex, x, x.getClass()); + } + + private boolean setTemporalObject(int parameterIndex, Object x) throws SQLException { if (x instanceof LocalDate) { setDate(parameterIndex, Date.valueOf((LocalDate) x)); - return; + return true; } if (x instanceof LocalTime) { setTime(parameterIndex, Time.valueOf((LocalTime) x)); - return; + return true; } if (x instanceof LocalDateTime) { setTimestamp(parameterIndex, Timestamp.valueOf((LocalDateTime) x)); - return; + return true; } if (x instanceof OffsetDateTime) { setTimestamp(parameterIndex, Timestamp.from(((OffsetDateTime) x).toInstant())); - return; + return true; } if (x instanceof Instant) { setTimestamp(parameterIndex, Timestamp.from((Instant) x)); - return; + return true; } if (x instanceof ZonedDateTime) { setTimestamp(parameterIndex, Timestamp.from(((ZonedDateTime) x).toInstant())); - return; + return true; } - this.parameterHandler.setParameter(parameterIndex, x, x.getClass()); + return false; } @Override From 5ad02ce287701f865b966c264d479a37609402cb Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Fri, 17 Jul 2026 16:21:34 -0400 Subject: [PATCH 4/5] fix(bigquery-jdbc): map LocalDateTime to DATETIME and add parameterHandler null check in getParameterClassName --- .../cloud/bigquery/jdbc/BigQueryJdbcTypeMappings.java | 3 ++- .../cloud/bigquery/jdbc/BigQueryParameterMetaData.java | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcTypeMappings.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcTypeMappings.java index 19b53d6e2ae7..1e87957d0831 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcTypeMappings.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcTypeMappings.java @@ -125,8 +125,9 @@ static StandardSQLTypeName classToType(Class type) return StandardSQLTypeName.NUMERIC; } else if (Date.class.isAssignableFrom(type) || LocalDate.class.isAssignableFrom(type)) { return StandardSQLTypeName.DATE; + } else if (LocalDateTime.class.isAssignableFrom(type)) { + return StandardSQLTypeName.DATETIME; } else if (Timestamp.class.isAssignableFrom(type) - || LocalDateTime.class.isAssignableFrom(type) || OffsetDateTime.class.isAssignableFrom(type) || Instant.class.isAssignableFrom(type) || ZonedDateTime.class.isAssignableFrom(type)) { 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 3c366765ffcf..945e28e02a48 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 @@ -120,9 +120,11 @@ public String getParameterClassName(int param) throws SQLException { return clazz.getName(); } } - Class boundType = this.parameterHandler.getType(param); - if (boundType != null) { - return boundType.getName(); + if (this.parameterHandler != null) { + Class boundType = this.parameterHandler.getType(param); + if (boundType != null) { + return boundType.getName(); + } } return Object.class.getName(); } From b0c2a45f54b8e4f36410777f70193e6f8681044f Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Tue, 21 Jul 2026 10:51:51 -0400 Subject: [PATCH 5/5] early returns --- .../jdbc/BigQueryJdbcTypeMappings.java | 48 +++++--- .../jdbc/BigQueryParameterMetaData.java | 103 ++++++++++-------- 2 files changed, 88 insertions(+), 63 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcTypeMappings.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcTypeMappings.java index 1e87957d0831..913cfb181bed 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcTypeMappings.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcTypeMappings.java @@ -109,40 +109,56 @@ static StandardSQLTypeName classToType(Class type) throws BigQueryJdbcSqlFeatureNotSupportedException { if (Boolean.class.isAssignableFrom(type)) { return StandardSQLTypeName.BOOL; - } else if (String.class.isAssignableFrom(type)) { + } + if (String.class.isAssignableFrom(type)) { return StandardSQLTypeName.STRING; - } else if (Integer.class.isAssignableFrom(type)) { + } + if (Integer.class.isAssignableFrom(type)) { return StandardSQLTypeName.INT64; - } else if (Long.class.isAssignableFrom(type)) { + } + if (Long.class.isAssignableFrom(type)) { return StandardSQLTypeName.INT64; - } else if (Short.class.isAssignableFrom(type)) { + } + if (Short.class.isAssignableFrom(type)) { return StandardSQLTypeName.INT64; - } else if (Double.class.isAssignableFrom(type)) { + } + if (Double.class.isAssignableFrom(type)) { return StandardSQLTypeName.FLOAT64; - } else if (Float.class.isAssignableFrom(type)) { + } + if (Float.class.isAssignableFrom(type)) { return StandardSQLTypeName.FLOAT64; - } else if (BigDecimal.class.isAssignableFrom(type)) { + } + if (BigDecimal.class.isAssignableFrom(type)) { return StandardSQLTypeName.NUMERIC; - } else if (Date.class.isAssignableFrom(type) || LocalDate.class.isAssignableFrom(type)) { + } + if (Date.class.isAssignableFrom(type) || LocalDate.class.isAssignableFrom(type)) { return StandardSQLTypeName.DATE; - } else if (LocalDateTime.class.isAssignableFrom(type)) { + } + if (LocalDateTime.class.isAssignableFrom(type)) { return StandardSQLTypeName.DATETIME; - } else if (Timestamp.class.isAssignableFrom(type) + } + if (Timestamp.class.isAssignableFrom(type) || OffsetDateTime.class.isAssignableFrom(type) || Instant.class.isAssignableFrom(type) || ZonedDateTime.class.isAssignableFrom(type)) { return StandardSQLTypeName.TIMESTAMP; - } else if (Time.class.isAssignableFrom(type) || LocalTime.class.isAssignableFrom(type)) { + } + if (Time.class.isAssignableFrom(type) || LocalTime.class.isAssignableFrom(type)) { return StandardSQLTypeName.TIME; - } else if (JsonObject.class.isAssignableFrom(type)) { + } + if (JsonObject.class.isAssignableFrom(type)) { return StandardSQLTypeName.JSON; - } else if (Byte.class.isAssignableFrom(type)) { + } + if (Byte.class.isAssignableFrom(type)) { return StandardSQLTypeName.INT64; - } else if (Array.class.isAssignableFrom(type)) { + } + if (Array.class.isAssignableFrom(type)) { return StandardSQLTypeName.ARRAY; - } else if (Struct.class.isAssignableFrom(type)) { + } + if (Struct.class.isAssignableFrom(type)) { return StandardSQLTypeName.STRUCT; - } else if (byte[].class.isAssignableFrom(type)) { + } + if (byte[].class.isAssignableFrom(type)) { return StandardSQLTypeName.BYTES; } throw new BigQueryJdbcSqlFeatureNotSupportedException( 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 945e28e02a48..d2b5dd4edae9 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 @@ -66,12 +66,13 @@ public boolean isSigned(int param) throws SQLException { public int getPrecision(int param) throws SQLException { checkValidIndex(param); StandardSQLTypeName sqlType = getStandardSQLTypeName(param); - if (sqlType != null) { - BigQueryJdbcTypeMappings.ColumnTypeInfo typeInfo = - BigQueryJdbcTypeMappings.STANDARD_TYPE_INFO.get(sqlType); - if (typeInfo != null && typeInfo.columnSize != null) { - return typeInfo.columnSize; - } + if (sqlType == null) { + return 0; + } + BigQueryJdbcTypeMappings.ColumnTypeInfo typeInfo = + BigQueryJdbcTypeMappings.STANDARD_TYPE_INFO.get(sqlType); + if (typeInfo != null && typeInfo.columnSize != null) { + return typeInfo.columnSize; } return 0; } @@ -80,12 +81,13 @@ public int getPrecision(int param) throws SQLException { public int getScale(int param) throws SQLException { checkValidIndex(param); StandardSQLTypeName sqlType = getStandardSQLTypeName(param); - if (sqlType != null) { - BigQueryJdbcTypeMappings.ColumnTypeInfo typeInfo = - BigQueryJdbcTypeMappings.STANDARD_TYPE_INFO.get(sqlType); - if (typeInfo != null && typeInfo.decimalDigits != null) { - return typeInfo.decimalDigits; - } + if (sqlType == null) { + return 0; + } + BigQueryJdbcTypeMappings.ColumnTypeInfo typeInfo = + BigQueryJdbcTypeMappings.STANDARD_TYPE_INFO.get(sqlType); + if (typeInfo != null && typeInfo.decimalDigits != null) { + return typeInfo.decimalDigits; } return 0; } @@ -94,11 +96,12 @@ public int getScale(int param) throws SQLException { public int getParameterType(int param) throws SQLException { checkValidIndex(param); StandardSQLTypeName sqlType = getStandardSQLTypeName(param); - if (sqlType != null) { - Integer jdbcType = BigQueryJdbcTypeMappings.standardSQLToJavaSqlTypesMapping.get(sqlType); - if (jdbcType != null) { - return jdbcType; - } + if (sqlType == null) { + return Types.OTHER; + } + Integer jdbcType = BigQueryJdbcTypeMappings.standardSQLToJavaSqlTypesMapping.get(sqlType); + if (jdbcType != null) { + return jdbcType; } return Types.OTHER; } @@ -120,11 +123,12 @@ public String getParameterClassName(int param) throws SQLException { return clazz.getName(); } } - if (this.parameterHandler != null) { - Class boundType = this.parameterHandler.getType(param); - if (boundType != null) { - return boundType.getName(); - } + if (this.parameterHandler == null) { + return Object.class.getName(); + } + Class boundType = this.parameterHandler.getType(param); + if (boundType != null) { + return boundType.getName(); } return Object.class.getName(); } @@ -132,42 +136,47 @@ public String getParameterClassName(int param) throws SQLException { @Override public int getParameterMode(int param) throws SQLException { checkValidIndex(param); - if (this.parameterHandler != null) { - BigQueryParameterHandler.BigQueryStatementParameterType paramType = - this.parameterHandler.getParameterType(param); - if (paramType == BigQueryParameterHandler.BigQueryStatementParameterType.OUT) { - return parameterModeOut; - } else if (paramType == BigQueryParameterHandler.BigQueryStatementParameterType.INOUT) { - return parameterModeInOut; - } + if (this.parameterHandler == null) { + return parameterModeIn; + } + BigQueryParameterHandler.BigQueryStatementParameterType paramType = + this.parameterHandler.getParameterType(param); + if (paramType == BigQueryParameterHandler.BigQueryStatementParameterType.OUT) { + return parameterModeOut; + } + if (paramType == BigQueryParameterHandler.BigQueryStatementParameterType.INOUT) { + return parameterModeInOut; } return parameterModeIn; } private StandardSQLTypeName getStandardSQLTypeName(int param) { - if (this.parameterHandler != null) { - StandardSQLTypeName sqlType = this.parameterHandler.getSqlType(param); - if (sqlType != null) { - return sqlType; - } - Class javaType = this.parameterHandler.getType(param); - if (javaType != null) { - try { - return BigQueryJdbcTypeMappings.classToType(javaType); - } catch (SQLException ignored) { - // fall back to default - } - } + if (this.parameterHandler == null) { + return null; + } + StandardSQLTypeName sqlType = this.parameterHandler.getSqlType(param); + if (sqlType != null) { + return sqlType; + } + Class javaType = this.parameterHandler.getType(param); + if (javaType == null) { + return null; + } + try { + return BigQueryJdbcTypeMappings.classToType(javaType); + } catch (SQLException ignored) { + // fall back to default + return null; } - return null; } @Override public T unwrap(Class iface) throws SQLException { - if (iface.isInstance(this)) { - return iface.cast(this); + if (!isWrapperFor(iface)) { + throw new BigQueryJdbcException( + "Cannot unwrap to " + (iface != null ? iface.getName() : "null")); } - throw new BigQueryJdbcException("Cannot unwrap to " + iface.getName()); + return iface.cast(this); } @Override