From 3f89f992c2010ec8246f8eaa5fc15d9a4001eaea Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Thu, 16 Jul 2026 12:36:43 -0400 Subject: [PATCH 01/16] feat(bigquery-jdbc): implement parameter setters in PreparedStatement --- .../jdbc/BigQueryJdbcTypeMappings.java | 15 +-- .../jdbc/BigQueryPreparedStatement.java | 56 +++++--- .../BigQueryPreparedStatementSettersTest.java | 125 ++++++++++++++++++ .../bigquery/jdbc/it/ITBigQueryJDBCTest.java | 26 ++-- 4 files changed, 190 insertions(+), 32 deletions(-) create mode 100644 java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryPreparedStatementSettersTest.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 795d60436b83..35ac15aae3cf 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 @@ -80,6 +80,8 @@ class BigQueryJdbcTypeMappings { ImmutableMap.ofEntries( entry(Types.BIGINT, Long.class), entry(Types.INTEGER, Integer.class), + entry(Types.SMALLINT, Long.class), + entry(Types.TINYINT, Long.class), entry(Types.BOOLEAN, Boolean.class), entry(Types.DOUBLE, Double.class), entry(Types.FLOAT, Float.class), @@ -94,7 +96,8 @@ class BigQueryJdbcTypeMappings { entry(Types.VARBINARY, byte[].class), entry(Types.STRUCT, Struct.class), entry(Types.BIT, Boolean.class), - entry(Types.ARRAY, Array.class)); + entry(Types.ARRAY, Array.class), + entry(Types.NULL, String.class)); static StandardSQLTypeName classToType(Class type) throws BigQueryJdbcSqlFeatureNotSupportedException { @@ -102,30 +105,24 @@ static StandardSQLTypeName classToType(Class type) return StandardSQLTypeName.BOOL; } else if (String.class.isAssignableFrom(type)) { return StandardSQLTypeName.STRING; - } else if (String.class.isAssignableFrom(type)) { - return StandardSQLTypeName.GEOGRAPHY; - } else if (String.class.isAssignableFrom(type)) { - return StandardSQLTypeName.DATETIME; } else if (Integer.class.isAssignableFrom(type)) { return StandardSQLTypeName.INT64; } else if (Long.class.isAssignableFrom(type)) { return StandardSQLTypeName.INT64; + } else if (Short.class.isAssignableFrom(type)) { + return StandardSQLTypeName.INT64; } else if (Double.class.isAssignableFrom(type)) { return StandardSQLTypeName.FLOAT64; } else if (Float.class.isAssignableFrom(type)) { return StandardSQLTypeName.FLOAT64; } else if (BigDecimal.class.isAssignableFrom(type)) { return StandardSQLTypeName.NUMERIC; - } else if (BigDecimal.class.isAssignableFrom(type)) { - return StandardSQLTypeName.BIGNUMERIC; } else if (Date.class.isAssignableFrom(type)) { return StandardSQLTypeName.DATE; } else if (Timestamp.class.isAssignableFrom(type)) { return StandardSQLTypeName.TIMESTAMP; } else if (Time.class.isAssignableFrom(type)) { return StandardSQLTypeName.TIME; - } else if (String.class.isAssignableFrom(type)) { - return StandardSQLTypeName.JSON; } else if (JsonObject.class.isAssignableFrom(type)) { return StandardSQLTypeName.JSON; } else if (Byte.class.isAssignableFrom(type)) { 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 7e62c7fd4f69..9a235e59d66c 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 @@ -55,6 +55,7 @@ import java.sql.SQLXML; import java.sql.Time; import java.sql.Timestamp; +import java.sql.Types; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -114,8 +115,15 @@ public void clearParameters() { } @Override - public void setNull(int parameterIndex, int sqlType) { - // TODO(neenu): implement null case + public void setNull(int parameterIndex, int sqlType) throws SQLException { + checkClosed(); + Class javaType; + try { + javaType = BigQueryJdbcTypeMappings.getJavaType(sqlType); + } catch (SQLException e) { + javaType = String.class; + } + this.parameterHandler.setParameter(parameterIndex, null, javaType); } @Override @@ -131,8 +139,9 @@ public void setByte(int parameterIndex, byte x) throws SQLException { } @Override - public void setShort(int parameterIndex, short x) { - // TODO(neenu): implement Bytes conversion. + public void setShort(int parameterIndex, short x) throws SQLException { + checkClosed(); + this.parameterHandler.setParameter(parameterIndex, (long) x, Long.class); } @Override @@ -172,8 +181,9 @@ public void setString(int parameterIndex, String x) throws SQLException { } @Override - public void setBytes(int parameterIndex, byte[] x) { - // TODO(neenu): implement Bytes conversion. + public void setBytes(int parameterIndex, byte[] x) throws SQLException { + checkClosed(); + this.parameterHandler.setParameter(parameterIndex, x, byte[].class); } @Override @@ -218,11 +228,24 @@ public void setBinaryStream(int parameterIndex, InputStream x, int length) { } @Override - public void setObject(int parameterIndex, Object x, int targetSqlType) {} + public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException { + checkClosed(); + if (x == null) { + setNull(parameterIndex, targetSqlType); + return; + } + Class javaType = BigQueryJdbcTypeMappings.getJavaType(targetSqlType); + this.parameterHandler.setParameter(parameterIndex, x, javaType); + } @Override - public void setObject(int parameterIndex, Object x) { - // TODO :NOT IMPLEMENTED + public void setObject(int parameterIndex, Object x) throws SQLException { + checkClosed(); + if (x == null) { + setNull(parameterIndex, Types.NULL); + return; + } + this.parameterHandler.setParameter(parameterIndex, x, x.getClass()); } @Override @@ -444,8 +467,9 @@ public void setClob(int parameterIndex, Clob x) { } @Override - public void setArray(int parameterIndex, Array x) { - // TODO(neenu) :IMPLEMENT ARRAY + public void setArray(int parameterIndex, Array x) throws SQLException { + checkClosed(); + this.parameterHandler.setParameter(parameterIndex, x, Array.class); } @Override @@ -470,8 +494,8 @@ public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) { } @Override - public void setNull(int parameterIndex, int sqlType, String typeName) { - // TODO :NOT IMPLEMENTED + public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException { + setNull(parameterIndex, sqlType); } @Override @@ -526,8 +550,10 @@ public void setSQLXML(int parameterIndex, SQLXML xmlObject) { } @Override - public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) { - // TODO(neenu) : IMPLEMENT? + public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) + throws SQLException { + checkClosed(); + setObject(parameterIndex, x, targetSqlType); } @Override 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 new file mode 100644 index 000000000000..edf0998c5965 --- /dev/null +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryPreparedStatementSettersTest.java @@ -0,0 +1,125 @@ +/* + * 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 org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.mock; + +import com.google.cloud.bigquery.StandardSQLTypeName; +import java.sql.Array; +import java.sql.Types; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class BigQueryPreparedStatementSettersTest { + + private BigQueryConnection connection; + private BigQueryPreparedStatement preparedStatement; + + @BeforeEach + public void setUp() throws Exception { + connection = mock(BigQueryConnection.class); + preparedStatement = new BigQueryPreparedStatement(connection, "SELECT ?, ?, ?, ?, ?"); + } + + @Test + public void testSetNullWithSqlType() throws Exception { + preparedStatement.setNull(1, Types.VARCHAR); + assertNull(preparedStatement.parameterHandler.getParameter(1)); + assertEquals(String.class, preparedStatement.parameterHandler.getType(1)); + assertEquals(StandardSQLTypeName.STRING, preparedStatement.parameterHandler.getSqlType(1)); + + preparedStatement.setNull(2, Types.INTEGER); + assertNull(preparedStatement.parameterHandler.getParameter(2)); + assertEquals(Integer.class, preparedStatement.parameterHandler.getType(2)); + assertEquals(StandardSQLTypeName.INT64, preparedStatement.parameterHandler.getSqlType(2)); + + preparedStatement.setNull(3, Types.NULL); + assertNull(preparedStatement.parameterHandler.getParameter(3)); + assertEquals(String.class, preparedStatement.parameterHandler.getType(3)); + assertEquals(StandardSQLTypeName.STRING, preparedStatement.parameterHandler.getSqlType(3)); + } + + @Test + public void testSetNullWithTypeName() throws Exception { + preparedStatement.setNull(1, Types.VARCHAR, "VARCHAR"); + assertNull(preparedStatement.parameterHandler.getParameter(1)); + assertEquals(String.class, preparedStatement.parameterHandler.getType(1)); + assertEquals(StandardSQLTypeName.STRING, preparedStatement.parameterHandler.getSqlType(1)); + } + + @Test + public void testSetShort() throws Exception { + preparedStatement.setShort(1, (short) 42); + assertEquals(42L, preparedStatement.parameterHandler.getParameter(1)); + assertEquals(Long.class, preparedStatement.parameterHandler.getType(1)); + assertEquals(StandardSQLTypeName.INT64, preparedStatement.parameterHandler.getSqlType(1)); + } + + @Test + public void testSetBytes() throws Exception { + byte[] bytes = new byte[] {1, 2, 3, 4}; + preparedStatement.setBytes(1, bytes); + assertArrayEquals(bytes, (byte[]) preparedStatement.parameterHandler.getParameter(1)); + assertEquals(byte[].class, preparedStatement.parameterHandler.getType(1)); + assertEquals(StandardSQLTypeName.BYTES, preparedStatement.parameterHandler.getSqlType(1)); + } + + @Test + public void testSetObjectSingleArg() throws Exception { + preparedStatement.setObject(1, "hello"); + assertEquals("hello", preparedStatement.parameterHandler.getParameter(1)); + assertEquals(String.class, preparedStatement.parameterHandler.getType(1)); + assertEquals(StandardSQLTypeName.STRING, preparedStatement.parameterHandler.getSqlType(1)); + + preparedStatement.setObject(2, 100L); + assertEquals(100L, preparedStatement.parameterHandler.getParameter(2)); + assertEquals(Long.class, preparedStatement.parameterHandler.getType(2)); + assertEquals(StandardSQLTypeName.INT64, preparedStatement.parameterHandler.getSqlType(2)); + + preparedStatement.setObject(3, null); + assertNull(preparedStatement.parameterHandler.getParameter(3)); + assertEquals(String.class, preparedStatement.parameterHandler.getType(3)); + } + + @Test + public void testSetObjectWithTargetSqlType() throws Exception { + preparedStatement.setObject(1, 123, Types.INTEGER); + assertEquals(123, preparedStatement.parameterHandler.getParameter(1)); + assertEquals(Integer.class, preparedStatement.parameterHandler.getType(1)); + assertEquals(StandardSQLTypeName.INT64, preparedStatement.parameterHandler.getSqlType(1)); + + preparedStatement.setObject(2, null, Types.INTEGER); + assertNull(preparedStatement.parameterHandler.getParameter(2)); + assertEquals(Integer.class, preparedStatement.parameterHandler.getType(2)); + + preparedStatement.setObject(3, 456, Types.INTEGER, 0); + assertEquals(456, preparedStatement.parameterHandler.getParameter(3)); + assertEquals(Integer.class, preparedStatement.parameterHandler.getType(3)); + } + + @Test + public void testSetArray() throws Exception { + Array mockArray = mock(Array.class); + preparedStatement.setArray(1, mockArray); + assertEquals(mockArray, preparedStatement.parameterHandler.getParameter(1)); + assertEquals(Array.class, preparedStatement.parameterHandler.getType(1)); + assertEquals(StandardSQLTypeName.ARRAY, preparedStatement.parameterHandler.getSqlType(1)); + } +} 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 47b0d0893f53..3fd1489c1afe 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 @@ -51,6 +51,7 @@ import java.sql.Statement; import java.sql.Time; import java.sql.Timestamp; +import java.sql.Types; import java.time.LocalTime; import java.util.Properties; import java.util.Random; @@ -984,11 +985,11 @@ public void testPreparedExecuteMethod() throws SQLException { String TABLE_NAME = "JDBC_PREPARED_EXECUTE_TABLE_" + randomNumber; String createQuery = String.format( - "CREATE OR REPLACE TABLE %s.%s (`StringField` STRING, `IntegerField` INTEGER);", + "CREATE OR REPLACE TABLE %s.%s (`StringField` STRING, `IntegerField` INTEGER, `ShortField` INT64, `BytesField` BYTES, `DoubleField` FLOAT64, `BooleanField` BOOL, `NullField` STRING);", DATASET, TABLE_NAME); String insertQuery = String.format( - "INSERT INTO %s.%s (StringField, IntegerField) VALUES (?,?), (?,?), (?,?), (?,?);", + "INSERT INTO %s.%s (StringField, IntegerField, ShortField, BytesField, DoubleField, BooleanField, NullField) VALUES (?,?,?,?,?,?,?), (?,?,?,?,?,?,?);", DATASET, TABLE_NAME); String updateQuery = String.format("UPDATE %s.%s SET StringField=? WHERE IntegerField=?", DATASET, TABLE_NAME); @@ -999,14 +1000,23 @@ public void testPreparedExecuteMethod() throws SQLException { assertFalse(createStatus); PreparedStatement insertStmt = bigQueryConnection.prepareStatement(insertQuery); + // Row 1: Testing setString, setInt, setShort, setBytes, setObject, setNull insertStmt.setString(1, "String1"); insertStmt.setInt(2, 111); - insertStmt.setString(3, "String2"); - insertStmt.setInt(4, 222); - insertStmt.setString(5, "String3"); - insertStmt.setInt(6, 333); - insertStmt.setString(7, "String4"); - insertStmt.setInt(8, 444); + insertStmt.setShort(3, (short) 12); + insertStmt.setBytes(4, new byte[] {0x1, 0x2}); + insertStmt.setObject(5, 3.14d); + insertStmt.setObject(6, true, Types.BOOLEAN); + insertStmt.setNull(7, Types.VARCHAR); + + // Row 2: Testing setString, setInt, setShort, setBytes, setObject, setNull with typeName + insertStmt.setString(8, "String2"); + insertStmt.setInt(9, 222); + insertStmt.setShort(10, (short) 34); + insertStmt.setBytes(11, new byte[] {0x3, 0x4}); + insertStmt.setObject(12, 6.28d); + insertStmt.setObject(13, false); + insertStmt.setNull(14, Types.VARCHAR, "STRING"); boolean insertStatus = insertStmt.execute(); assertFalse(insertStatus); From 6a9191d777b8e93ec19dd42906de474810b6e60d Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Thu, 16 Jul 2026 12:58:29 -0400 Subject: [PATCH 02/16] address commets --- .../jdbc/BigQueryJdbcTypeMappings.java | 6 +- .../jdbc/BigQueryPreparedStatement.java | 111 +++++++++--------- .../BigQueryPreparedStatementSettersTest.java | 26 +++- 3 files changed, 82 insertions(+), 61 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 35ac15aae3cf..648cf8fd4673 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 @@ -80,8 +80,8 @@ class BigQueryJdbcTypeMappings { ImmutableMap.ofEntries( entry(Types.BIGINT, Long.class), entry(Types.INTEGER, Integer.class), - entry(Types.SMALLINT, Long.class), - entry(Types.TINYINT, Long.class), + entry(Types.SMALLINT, Short.class), + entry(Types.TINYINT, Byte.class), entry(Types.BOOLEAN, Boolean.class), entry(Types.DOUBLE, Double.class), entry(Types.FLOAT, Float.class), @@ -126,7 +126,7 @@ static StandardSQLTypeName classToType(Class type) } else if (JsonObject.class.isAssignableFrom(type)) { return StandardSQLTypeName.JSON; } else if (Byte.class.isAssignableFrom(type)) { - return StandardSQLTypeName.BYTES; + return StandardSQLTypeName.INT64; } else if (Array.class.isAssignableFrom(type)) { return StandardSQLTypeName.ARRAY; } else if (Struct.class.isAssignableFrom(type)) { 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 9a235e59d66c..12b450d044b1 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 @@ -27,6 +27,7 @@ import com.google.cloud.bigquery.TableId; import com.google.cloud.bigquery.exception.BigQueryJdbcException; import com.google.cloud.bigquery.exception.BigQueryJdbcRuntimeException; +import com.google.cloud.bigquery.exception.BigQueryJdbcSqlFeatureNotSupportedException; import com.google.cloud.bigquery.storage.v1.BatchCommitWriteStreamsRequest; import com.google.cloud.bigquery.storage.v1.BatchCommitWriteStreamsResponse; import com.google.cloud.bigquery.storage.v1.BigQueryWriteClient; @@ -117,12 +118,7 @@ public void clearParameters() { @Override public void setNull(int parameterIndex, int sqlType) throws SQLException { checkClosed(); - Class javaType; - try { - javaType = BigQueryJdbcTypeMappings.getJavaType(sqlType); - } catch (SQLException e) { - javaType = String.class; - } + Class javaType = BigQueryJdbcTypeMappings.getJavaType(sqlType); this.parameterHandler.setParameter(parameterIndex, null, javaType); } @@ -141,7 +137,7 @@ public void setByte(int parameterIndex, byte x) throws SQLException { @Override public void setShort(int parameterIndex, short x) throws SQLException { checkClosed(); - this.parameterHandler.setParameter(parameterIndex, (long) x, Long.class); + this.parameterHandler.setParameter(parameterIndex, x, Short.class); } @Override @@ -447,23 +443,24 @@ Boolean useWriteAPI() { } @Override - public void setCharacterStream(int parameterIndex, Reader reader, int length) { - // TODO :NOT IMPLEMENTED + public void setCharacterStream(int parameterIndex, Reader reader, int length) + throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setCharacterStream is not supported."); } @Override - public void setRef(int parameterIndex, Ref x) { - // TODO :NOT IMPLEMENTED + public void setRef(int parameterIndex, Ref x) throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setRef is not supported."); } @Override - public void setBlob(int parameterIndex, Blob x) { - // TODO :NOT IMPLEMENTED + public void setBlob(int parameterIndex, Blob x) throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setBlob is not supported."); } @Override - public void setClob(int parameterIndex, Clob x) { - // TODO :NOT IMPLEMENTED + public void setClob(int parameterIndex, Clob x) throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setClob is not supported."); } @Override @@ -499,8 +496,8 @@ public void setNull(int parameterIndex, int sqlType, String typeName) throws SQL } @Override - public void setURL(int parameterIndex, URL x) { - // TODO :NOT IMPLEMENTED + public void setURL(int parameterIndex, URL x) throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setURL is not supported."); } @Override @@ -510,45 +507,52 @@ public ParameterMetaData getParameterMetaData() { } @Override - public void setRowId(int parameterIndex, RowId x) { - // TODO :NOT IMPLEMENTED + public void setRowId(int parameterIndex, RowId x) throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setRowId is not supported."); } @Override - public void setNString(int parameterIndex, String value) { - // TODO :NOT IMPLEMENTED + public void setNString(int parameterIndex, String value) throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setNString is not supported."); } @Override - public void setNCharacterStream(int parameterIndex, Reader value, long length) { - // TODO :NOT IMPLEMENTED + public void setNCharacterStream(int parameterIndex, Reader value, long length) + throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setNCharacterStream is not supported."); } @Override - public void setNClob(int parameterIndex, NClob value) { - // TODO :NOT IMPLEMENTED + public void setNClob(int parameterIndex, NClob value) throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setNClob is not supported."); } @Override - public void setClob(int parameterIndex, Reader reader, long length) { - // TODO :NOT IMPLEMENTED + public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setClob is not supported."); } @Override - public void setBlob(int parameterIndex, InputStream inputStream, long length) { - // TODO :NOT IMPLEMENTED + public void setBlob(int parameterIndex, InputStream inputStream, long length) + throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setBlob is not supported."); } @Override - public void setNClob(int parameterIndex, Reader reader, long length) { - // TODO :NOT IMPLEMENTED + public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setNClob is not supported."); } @Override - public void setSQLXML(int parameterIndex, SQLXML xmlObject) { - // TODO :NOT IMPLEMENTED + public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setSQLXML is not supported."); } + /** + * Note: BigQuery handles numeric scale and precision dynamically for NUMERIC (DECIMAL) and + * BIGNUMERIC data types. The scaleOrLength parameter is ignored and delegates directly to {@link + * #setObject(int, Object, int)}. + */ @Override public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException { @@ -557,52 +561,53 @@ public void setObject(int parameterIndex, Object x, int targetSqlType, int scale } @Override - public void setAsciiStream(int parameterIndex, InputStream x, long length) { - // TODO :NOT IMPLEMENTED + public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setAsciiStream is not supported."); } @Override - public void setBinaryStream(int parameterIndex, InputStream x, long length) { - // TODO :NOT IMPLEMENTED + public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setBinaryStream is not supported."); } @Override - public void setCharacterStream(int parameterIndex, Reader reader, long length) { - // TODO :NOT IMPLEMENTED + public void setCharacterStream(int parameterIndex, Reader reader, long length) + throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setCharacterStream is not supported."); } @Override - public void setAsciiStream(int parameterIndex, InputStream x) { - // TODO :NOT IMPLEMENTED + public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setAsciiStream is not supported."); } @Override - public void setBinaryStream(int parameterIndex, InputStream x) { - // TODO :NOT IMPLEMENTED + public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setBinaryStream is not supported."); } @Override - public void setCharacterStream(int parameterIndex, Reader reader) { - // TODO :NOT IMPLEMENTED + public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setCharacterStream is not supported."); } @Override - public void setNCharacterStream(int parameterIndex, Reader value) { - // TODO :NOT IMPLEMENTED + public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setNCharacterStream is not supported."); } @Override - public void setClob(int parameterIndex, Reader reader) { - // TODO :NOT IMPLEMENTED + public void setClob(int parameterIndex, Reader reader) throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setClob is not supported."); } @Override - public void setBlob(int parameterIndex, InputStream inputStream) { - // TODO :NOT IMPLEMENTED + public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setBlob is not supported."); } @Override - public void setNClob(int parameterIndex, Reader reader) { - // TODO :NOT IMPLEMENTED + public void setNClob(int parameterIndex, Reader reader) throws SQLException { + throw new BigQueryJdbcSqlFeatureNotSupportedException("setNClob is not supported."); } } 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 edf0998c5965..f9b07efabd90 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 @@ -19,6 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; 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.StandardSQLTypeName; @@ -50,10 +51,20 @@ public void testSetNullWithSqlType() throws Exception { assertEquals(Integer.class, preparedStatement.parameterHandler.getType(2)); assertEquals(StandardSQLTypeName.INT64, preparedStatement.parameterHandler.getSqlType(2)); - preparedStatement.setNull(3, Types.NULL); + preparedStatement.setNull(3, Types.SMALLINT); assertNull(preparedStatement.parameterHandler.getParameter(3)); - assertEquals(String.class, preparedStatement.parameterHandler.getType(3)); - assertEquals(StandardSQLTypeName.STRING, preparedStatement.parameterHandler.getSqlType(3)); + assertEquals(Short.class, preparedStatement.parameterHandler.getType(3)); + assertEquals(StandardSQLTypeName.INT64, preparedStatement.parameterHandler.getSqlType(3)); + + preparedStatement.setNull(4, Types.TINYINT); + assertNull(preparedStatement.parameterHandler.getParameter(4)); + assertEquals(Byte.class, preparedStatement.parameterHandler.getType(4)); + assertEquals(StandardSQLTypeName.INT64, preparedStatement.parameterHandler.getSqlType(4)); + + preparedStatement.setNull(5, Types.NULL); + assertNull(preparedStatement.parameterHandler.getParameter(5)); + assertEquals(String.class, preparedStatement.parameterHandler.getType(5)); + assertEquals(StandardSQLTypeName.STRING, preparedStatement.parameterHandler.getSqlType(5)); } @Test @@ -64,11 +75,16 @@ public void testSetNullWithTypeName() throws Exception { assertEquals(StandardSQLTypeName.STRING, preparedStatement.parameterHandler.getSqlType(1)); } + @Test + public void testSetNullWithUnsupportedSqlTypeThrowsException() { + assertThrows(java.sql.SQLException.class, () -> preparedStatement.setNull(1, 99999)); + } + @Test public void testSetShort() throws Exception { preparedStatement.setShort(1, (short) 42); - assertEquals(42L, preparedStatement.parameterHandler.getParameter(1)); - assertEquals(Long.class, preparedStatement.parameterHandler.getType(1)); + assertEquals((short) 42, preparedStatement.parameterHandler.getParameter(1)); + assertEquals(Short.class, preparedStatement.parameterHandler.getType(1)); assertEquals(StandardSQLTypeName.INT64, preparedStatement.parameterHandler.getSqlType(1)); } From defb44a1b1dac35ba7c9b18808b7be78ade263ce Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Thu, 16 Jul 2026 13:14:44 -0400 Subject: [PATCH 03/16] feat(bigquery-jdbc): implement PreparedStatement ParameterMetaData and Calendar date setters --- .../jdbc/BigQueryParameterMetaData.java | 166 ++++++++++++++++++ .../jdbc/BigQueryPreparedStatement.java | 80 +++++++-- .../BigQueryPreparedStatementSettersTest.java | 73 ++++++++ .../bigquery/jdbc/it/ITBigQueryJDBCTest.java | 27 ++- 4 files changed, 333 insertions(+), 13 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/BigQueryParameterMetaData.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryParameterMetaData.java new file mode 100644 index 000000000000..89f67fba04e9 --- /dev/null +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryParameterMetaData.java @@ -0,0 +1,166 @@ +/* + * 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); + 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); + } +} 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..b32b0d01a0fc 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 @@ -70,7 +70,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) { @@ -470,24 +470,80 @@ 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; + } + if (cal == null) { + setDate(parameterIndex, x); + return; + } + Calendar defaultCal = Calendar.getInstance(); + defaultCal.setTime(x); + cal.set(Calendar.YEAR, defaultCal.get(Calendar.YEAR)); + cal.set(Calendar.MONTH, defaultCal.get(Calendar.MONTH)); + cal.set(Calendar.DAY_OF_MONTH, defaultCal.get(Calendar.DAY_OF_MONTH)); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + setDate(parameterIndex, new Date(cal.getTimeInMillis())); } @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; + } + if (cal == null) { + setTime(parameterIndex, x); + return; + } + Calendar defaultCal = Calendar.getInstance(); + defaultCal.setTime(x); + cal.set(Calendar.HOUR_OF_DAY, defaultCal.get(Calendar.HOUR_OF_DAY)); + cal.set(Calendar.MINUTE, defaultCal.get(Calendar.MINUTE)); + cal.set(Calendar.SECOND, defaultCal.get(Calendar.SECOND)); + cal.set(Calendar.MILLISECOND, defaultCal.get(Calendar.MILLISECOND)); + setTime(parameterIndex, new Time(cal.getTimeInMillis())); } @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; + } + if (cal == null) { + setTimestamp(parameterIndex, x); + return; + } + Calendar defaultCal = Calendar.getInstance(); + defaultCal.setTime(x); + cal.set(Calendar.YEAR, defaultCal.get(Calendar.YEAR)); + cal.set(Calendar.MONTH, defaultCal.get(Calendar.MONTH)); + cal.set(Calendar.DAY_OF_MONTH, defaultCal.get(Calendar.DAY_OF_MONTH)); + cal.set(Calendar.HOUR_OF_DAY, defaultCal.get(Calendar.HOUR_OF_DAY)); + cal.set(Calendar.MINUTE, defaultCal.get(Calendar.MINUTE)); + cal.set(Calendar.SECOND, defaultCal.get(Calendar.SECOND)); + cal.set(Calendar.MILLISECOND, defaultCal.get(Calendar.MILLISECOND)); + Timestamp adjustedTimestamp = new Timestamp(cal.getTimeInMillis()); + adjustedTimestamp.setNanos(x.getNanos()); + setTimestamp(parameterIndex, adjustedTimestamp); } @Override @@ -501,9 +557,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/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..784ad5c23d9f 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,12 +18,16 @@ 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.ResultSetMetaData; import java.sql.Types; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -138,4 +142,73 @@ 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); + + java.sql.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(java.sql.ParameterMetaData.parameterNullable, metaData.isNullable(1)); + assertEquals(java.sql.ParameterMetaData.parameterModeIn, metaData.getParameterMode(1)); + + assertThrows(java.sql.SQLException.class, () -> metaData.getParameterType(0)); + assertThrows(java.sql.SQLException.class, () -> metaData.getParameterType(6)); + } + + @Test + public void testSetDateWithCalendar() throws Exception { + java.util.Calendar cal = java.util.Calendar.getInstance(java.util.TimeZone.getTimeZone("UTC")); + java.sql.Date date = new java.sql.Date(1700000000000L); // 2023-11-14 + + preparedStatement.setDate(1, date, cal); + assertEquals(String.class, preparedStatement.parameterHandler.getType(1)); + } + + @Test + public void testSetTimeWithCalendar() throws Exception { + java.util.Calendar cal = java.util.Calendar.getInstance(java.util.TimeZone.getTimeZone("UTC")); + java.sql.Time time = new java.sql.Time(43200000L); // 12:00:00 + + preparedStatement.setTime(1, time, cal); + assertEquals(String.class, preparedStatement.parameterHandler.getType(1)); + } + + @Test + public void testSetTimestampWithCalendar() throws Exception { + java.util.Calendar cal = java.util.Calendar.getInstance(java.util.TimeZone.getTimeZone("UTC")); + java.sql.Timestamp ts = new java.sql.Timestamp(1700000000000L); + + preparedStatement.setTimestamp(1, ts, cal); + assertEquals(String.class, preparedStatement.parameterHandler.getType(1)); + } + + @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)); + } } 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 12348a6ade7844d98ec989afea7e89204659fe08 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Thu, 16 Jul 2026 13:51:19 -0400 Subject: [PATCH 04/16] add date/time handling in setObject --- .../jdbc/BigQueryJdbcTypeMappings.java | 16 ++++-- .../jdbc/BigQueryPreparedStatement.java | 54 +++++++++++++++++++ .../BigQueryPreparedStatementSettersTest.java | 52 ++++++++++++++---- 3 files changed, 108 insertions(+), 14 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 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/BigQueryPreparedStatement.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryPreparedStatement.java index b32b0d01a0fc..01f95bfa70ab 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; @@ -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()); } 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 784ad5c23d9f..b8a2ba55eb5b 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 @@ -27,8 +27,19 @@ 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; @@ -148,7 +159,7 @@ public void testGetParameterMetaData() throws Exception { preparedStatement.setString(1, "test"); preparedStatement.setInt(2, 42); - java.sql.ParameterMetaData metaData = preparedStatement.getParameterMetaData(); + ParameterMetaData metaData = preparedStatement.getParameterMetaData(); assertEquals(5, metaData.getParameterCount()); assertEquals(Types.NVARCHAR, metaData.getParameterType(1)); assertEquals("STRING", metaData.getParameterTypeName(1)); @@ -158,17 +169,17 @@ public void testGetParameterMetaData() throws Exception { assertEquals("INT64", metaData.getParameterTypeName(2)); assertEquals(Long.class.getName(), metaData.getParameterClassName(2)); - assertEquals(java.sql.ParameterMetaData.parameterNullable, metaData.isNullable(1)); - assertEquals(java.sql.ParameterMetaData.parameterModeIn, metaData.getParameterMode(1)); + assertEquals(ParameterMetaData.parameterNullable, metaData.isNullable(1)); + assertEquals(ParameterMetaData.parameterModeIn, metaData.getParameterMode(1)); - assertThrows(java.sql.SQLException.class, () -> metaData.getParameterType(0)); - assertThrows(java.sql.SQLException.class, () -> metaData.getParameterType(6)); + assertThrows(SQLException.class, () -> metaData.getParameterType(0)); + assertThrows(SQLException.class, () -> metaData.getParameterType(6)); } @Test public void testSetDateWithCalendar() throws Exception { - java.util.Calendar cal = java.util.Calendar.getInstance(java.util.TimeZone.getTimeZone("UTC")); - java.sql.Date date = new java.sql.Date(1700000000000L); // 2023-11-14 + Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + Date date = new Date(1700000000000L); // 2023-11-14 preparedStatement.setDate(1, date, cal); assertEquals(String.class, preparedStatement.parameterHandler.getType(1)); @@ -176,8 +187,8 @@ public void testSetDateWithCalendar() throws Exception { @Test public void testSetTimeWithCalendar() throws Exception { - java.util.Calendar cal = java.util.Calendar.getInstance(java.util.TimeZone.getTimeZone("UTC")); - java.sql.Time time = new java.sql.Time(43200000L); // 12:00:00 + Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + Time time = new Time(43200000L); // 12:00:00 preparedStatement.setTime(1, time, cal); assertEquals(String.class, preparedStatement.parameterHandler.getType(1)); @@ -185,8 +196,8 @@ public void testSetTimeWithCalendar() throws Exception { @Test public void testSetTimestampWithCalendar() throws Exception { - java.util.Calendar cal = java.util.Calendar.getInstance(java.util.TimeZone.getTimeZone("UTC")); - java.sql.Timestamp ts = new java.sql.Timestamp(1700000000000L); + Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + Timestamp ts = new Timestamp(1700000000000L); preparedStatement.setTimestamp(1, ts, cal); assertEquals(String.class, preparedStatement.parameterHandler.getType(1)); @@ -211,4 +222,23 @@ public void testGetMetaData() throws Exception { 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)); + } } From 1e293fb42078140da6808e78610a8b8079e909a1 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Thu, 16 Jul 2026 13:54:19 -0400 Subject: [PATCH 05/16] fix(jdbc): clone Calendar parameter in date/time setters to prevent mutation --- .../jdbc/BigQueryPreparedStatement.java | 45 ++++++++++--------- .../BigQueryPreparedStatementSettersTest.java | 9 ++++ 2 files changed, 33 insertions(+), 21 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 01f95bfa70ab..09e636125bc4 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 @@ -543,16 +543,17 @@ public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLExceptio setDate(parameterIndex, x); return; } + Calendar targetCal = (Calendar) cal.clone(); Calendar defaultCal = Calendar.getInstance(); defaultCal.setTime(x); - cal.set(Calendar.YEAR, defaultCal.get(Calendar.YEAR)); - cal.set(Calendar.MONTH, defaultCal.get(Calendar.MONTH)); - cal.set(Calendar.DAY_OF_MONTH, defaultCal.get(Calendar.DAY_OF_MONTH)); - cal.set(Calendar.HOUR_OF_DAY, 0); - cal.set(Calendar.MINUTE, 0); - cal.set(Calendar.SECOND, 0); - cal.set(Calendar.MILLISECOND, 0); - setDate(parameterIndex, new Date(cal.getTimeInMillis())); + targetCal.set(Calendar.YEAR, defaultCal.get(Calendar.YEAR)); + targetCal.set(Calendar.MONTH, defaultCal.get(Calendar.MONTH)); + targetCal.set(Calendar.DAY_OF_MONTH, defaultCal.get(Calendar.DAY_OF_MONTH)); + targetCal.set(Calendar.HOUR_OF_DAY, 0); + targetCal.set(Calendar.MINUTE, 0); + targetCal.set(Calendar.SECOND, 0); + targetCal.set(Calendar.MILLISECOND, 0); + setDate(parameterIndex, new Date(targetCal.getTimeInMillis())); } @Override @@ -566,13 +567,14 @@ public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLExceptio setTime(parameterIndex, x); return; } + Calendar targetCal = (Calendar) cal.clone(); Calendar defaultCal = Calendar.getInstance(); defaultCal.setTime(x); - cal.set(Calendar.HOUR_OF_DAY, defaultCal.get(Calendar.HOUR_OF_DAY)); - cal.set(Calendar.MINUTE, defaultCal.get(Calendar.MINUTE)); - cal.set(Calendar.SECOND, defaultCal.get(Calendar.SECOND)); - cal.set(Calendar.MILLISECOND, defaultCal.get(Calendar.MILLISECOND)); - setTime(parameterIndex, new Time(cal.getTimeInMillis())); + 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)); + setTime(parameterIndex, new Time(targetCal.getTimeInMillis())); } @Override @@ -586,16 +588,17 @@ public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws S setTimestamp(parameterIndex, x); return; } + Calendar targetCal = (Calendar) cal.clone(); Calendar defaultCal = Calendar.getInstance(); defaultCal.setTime(x); - cal.set(Calendar.YEAR, defaultCal.get(Calendar.YEAR)); - cal.set(Calendar.MONTH, defaultCal.get(Calendar.MONTH)); - cal.set(Calendar.DAY_OF_MONTH, defaultCal.get(Calendar.DAY_OF_MONTH)); - cal.set(Calendar.HOUR_OF_DAY, defaultCal.get(Calendar.HOUR_OF_DAY)); - cal.set(Calendar.MINUTE, defaultCal.get(Calendar.MINUTE)); - cal.set(Calendar.SECOND, defaultCal.get(Calendar.SECOND)); - cal.set(Calendar.MILLISECOND, defaultCal.get(Calendar.MILLISECOND)); - Timestamp adjustedTimestamp = new Timestamp(cal.getTimeInMillis()); + targetCal.set(Calendar.YEAR, defaultCal.get(Calendar.YEAR)); + targetCal.set(Calendar.MONTH, defaultCal.get(Calendar.MONTH)); + targetCal.set(Calendar.DAY_OF_MONTH, defaultCal.get(Calendar.DAY_OF_MONTH)); + 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)); + Timestamp adjustedTimestamp = new Timestamp(targetCal.getTimeInMillis()); adjustedTimestamp.setNanos(x.getNanos()); setTimestamp(parameterIndex, adjustedTimestamp); } 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 b8a2ba55eb5b..9a019cab7810 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 @@ -179,28 +179,37 @@ public void testGetParameterMetaData() throws Exception { @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 From 5e3e7d059975df0bee3bbfaa221f6939760fb9eb Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Thu, 16 Jul 2026 14:10:28 -0400 Subject: [PATCH 06/16] fix short conversion --- .../bigquery/jdbc/BigQueryParameterHandler.java | 11 +++++++++++ .../jdbc/BigQueryParameterHandlerTest.java | 17 +++++++++++++++++ 2 files changed, 28 insertions(+) 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 9cb60177090c..026e68943cfc 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 @@ -21,6 +21,7 @@ import com.google.cloud.bigquery.StandardSQLTypeName; import com.google.cloud.bigquery.exception.BigQueryJdbcException; import com.google.cloud.bigquery.exception.BigQueryJdbcSqlFeatureNotSupportedException; +import java.math.BigInteger; import java.sql.SQLException; import java.util.ArrayList; @@ -59,6 +60,16 @@ QueryJobConfiguration.Builder configureParameters( Object parameterValue = getParameter(i); StandardSQLTypeName sqlType = getSqlType(i); + if (parameterValue != null) { + if (sqlType == StandardSQLTypeName.INT64 + && (parameterValue instanceof Short + || parameterValue instanceof Byte + || parameterValue instanceof BigInteger)) { + parameterValue = ((Number) parameterValue).longValue(); + } else if (sqlType == StandardSQLTypeName.FLOAT64 && parameterValue instanceof Float) { + parameterValue = ((Number) parameterValue).doubleValue(); + } + } LOG.finest( "Parameter %s of type %s at index %s added to QueryJobConfiguration", parameterValue, sqlType, i); diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java index 961c59ecf7b7..ab7fa7fb1c7a 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java @@ -19,6 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import com.google.cloud.bigquery.QueryJobConfiguration; import com.google.cloud.bigquery.StandardSQLTypeName; import com.google.cloud.bigquery.jdbc.BigQueryParameterHandler.BigQueryStatementParameterType; import org.junit.jupiter.api.Test; @@ -139,4 +140,20 @@ public void testGetSetParameterByIndex() throws Exception { assertEquals(String.class, paramHandler.getType(2)); assertEquals(StandardSQLTypeName.STRING, paramHandler.getSqlType(2)); } + + @Test + public void testConfigureParametersWidenNumericTypes() throws Exception { + BigQueryParameterHandler paramHandler = new BigQueryParameterHandler(3); + paramHandler.setParameter(1, (short) 5, Short.class); + paramHandler.setParameter(2, (byte) 10, Byte.class); + paramHandler.setParameter(3, 3.14f, Float.class); + + QueryJobConfiguration.Builder builder = QueryJobConfiguration.newBuilder("SELECT 1"); + paramHandler.configureParameters(builder); + + QueryJobConfiguration config = builder.build(); + assertEquals(3, config.getPositionalParameters().size()); + assertEquals("5", config.getPositionalParameters().get(0).getValue()); + assertEquals("10", config.getPositionalParameters().get(1).getValue()); + } } From b3ca772d4c80bcef50e018033a0e8300f84fa80b Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Fri, 17 Jul 2026 12:31:03 -0400 Subject: [PATCH 07/16] feat(jdbc): support dynamic parameter modes (IN/OUT/INOUT) in BigQueryParameterMetaData --- .../bigquery/jdbc/BigQueryParameterMetaData.java | 9 +++++++++ .../BigQueryPreparedStatementSettersTest.java | 15 +++++++++++++++ 2 files changed, 24 insertions(+) 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 89f67fba04e9..3c366765ffcf 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 @@ -130,6 +130,15 @@ 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; + } + } return parameterModeIn; } 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 9a019cab7810..d1703f1f3f18 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 @@ -176,6 +176,21 @@ public void testGetParameterMetaData() throws Exception { 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")); From 6ea936b6f1089de5c58ef2059996117aa18c4378 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Fri, 17 Jul 2026 13:17:57 -0400 Subject: [PATCH 08/16] feat(bigquery-jdbc): centralize Calendar date time conversion helpers in BigQueryTypeCoercionUtility --- .../jdbc/BigQueryParameterHandler.java | 23 +++++ .../jdbc/BigQueryPreparedStatement.java | 47 +--------- .../jdbc/BigQueryTypeCoercionUtility.java | 87 +++++++++++++++++++ .../jdbc/BigQueryParameterHandlerTest.java | 36 ++++++++ 4 files changed, 149 insertions(+), 44 deletions(-) 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 026e68943cfc..c46ea697be6a 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 @@ -22,7 +22,10 @@ import com.google.cloud.bigquery.exception.BigQueryJdbcException; import com.google.cloud.bigquery.exception.BigQueryJdbcSqlFeatureNotSupportedException; import java.math.BigInteger; +import java.sql.Date; import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; import java.util.ArrayList; class BigQueryParameterHandler { @@ -299,4 +302,24 @@ StandardSQLTypeName getSqlType(String name) { int getParametersArraySize() { return this.parametersArraySize; } + + /** + * Returns a safe, cloned copy of the provided Calendar to prevent mutating the caller's state + * during timezone conversions. If cal is null, returns a default Calendar instance. + */ + static Calendar getSafeCalendar(Calendar cal) { + return BigQueryTypeCoercionUtility.getSafeCalendar(cal); + } + + static Date convertDateWithCalendar(Date date, Calendar cal) { + return BigQueryTypeCoercionUtility.convertDateWithCalendar(date, cal); + } + + static Time convertTimeWithCalendar(Time time, Calendar cal) { + return BigQueryTypeCoercionUtility.convertTimeWithCalendar(time, cal); + } + + static Timestamp convertTimestampWithCalendar(Timestamp timestamp, Calendar cal) { + return BigQueryTypeCoercionUtility.convertTimestampWithCalendar(timestamp, cal); + } } 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 09e636125bc4..2170ebc0143a 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 @@ -539,21 +539,7 @@ public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLExceptio setNull(parameterIndex, Types.DATE); return; } - if (cal == null) { - setDate(parameterIndex, x); - return; - } - Calendar targetCal = (Calendar) cal.clone(); - Calendar defaultCal = Calendar.getInstance(); - defaultCal.setTime(x); - targetCal.set(Calendar.YEAR, defaultCal.get(Calendar.YEAR)); - targetCal.set(Calendar.MONTH, defaultCal.get(Calendar.MONTH)); - targetCal.set(Calendar.DAY_OF_MONTH, defaultCal.get(Calendar.DAY_OF_MONTH)); - targetCal.set(Calendar.HOUR_OF_DAY, 0); - targetCal.set(Calendar.MINUTE, 0); - targetCal.set(Calendar.SECOND, 0); - targetCal.set(Calendar.MILLISECOND, 0); - setDate(parameterIndex, new Date(targetCal.getTimeInMillis())); + setDate(parameterIndex, BigQueryTypeCoercionUtility.convertDateWithCalendar(x, cal)); } @Override @@ -563,18 +549,7 @@ public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLExceptio setNull(parameterIndex, Types.TIME); return; } - if (cal == null) { - setTime(parameterIndex, x); - return; - } - Calendar targetCal = (Calendar) cal.clone(); - Calendar defaultCal = Calendar.getInstance(); - defaultCal.setTime(x); - 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)); - setTime(parameterIndex, new Time(targetCal.getTimeInMillis())); + setTime(parameterIndex, BigQueryTypeCoercionUtility.convertTimeWithCalendar(x, cal)); } @Override @@ -584,23 +559,7 @@ public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws S setNull(parameterIndex, Types.TIMESTAMP); return; } - if (cal == null) { - setTimestamp(parameterIndex, x); - return; - } - Calendar targetCal = (Calendar) cal.clone(); - Calendar defaultCal = Calendar.getInstance(); - defaultCal.setTime(x); - targetCal.set(Calendar.YEAR, defaultCal.get(Calendar.YEAR)); - targetCal.set(Calendar.MONTH, defaultCal.get(Calendar.MONTH)); - targetCal.set(Calendar.DAY_OF_MONTH, defaultCal.get(Calendar.DAY_OF_MONTH)); - 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)); - Timestamp adjustedTimestamp = new Timestamp(targetCal.getTimeInMillis()); - adjustedTimestamp.setNanos(x.getNanos()); - setTimestamp(parameterIndex, adjustedTimestamp); + setTimestamp(parameterIndex, BigQueryTypeCoercionUtility.convertTimestampWithCalendar(x, cal)); } @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..0df7950fbc4b 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,11 @@ import java.time.LocalTime; import java.time.OffsetDateTime; import java.time.Period; +import java.time.ZoneId; import java.time.ZoneOffset; 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 +45,91 @@ 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(); + } + return (Calendar) cal.clone(); + } + + /** + * 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) { + return null; + } + if (cal == null) { + return date; + } + Calendar defaultCal = Calendar.getInstance(); + defaultCal.setTime(date); + + Calendar targetCal = (Calendar) cal.clone(); + targetCal.set(Calendar.YEAR, defaultCal.get(Calendar.YEAR)); + targetCal.set(Calendar.MONTH, defaultCal.get(Calendar.MONTH)); + targetCal.set(Calendar.DAY_OF_MONTH, defaultCal.get(Calendar.DAY_OF_MONTH)); + targetCal.set(Calendar.HOUR_OF_DAY, 0); + targetCal.set(Calendar.MINUTE, 0); + targetCal.set(Calendar.SECOND, 0); + targetCal.set(Calendar.MILLISECOND, 0); + return new Date(targetCal.getTimeInMillis()); + } + + /** + * 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) { + return null; + } + if (cal == null) { + return time; + } + Calendar defaultCal = Calendar.getInstance(); + defaultCal.setTime(time); + + Calendar targetCal = (Calendar) cal.clone(); + 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) { + return null; + } + if (cal == null) { + return timestamp; + } + Calendar defaultCal = Calendar.getInstance(); + defaultCal.setTime(timestamp); + + Calendar targetCal = (Calendar) cal.clone(); + targetCal.set(Calendar.YEAR, defaultCal.get(Calendar.YEAR)); + targetCal.set(Calendar.MONTH, defaultCal.get(Calendar.MONTH)); + targetCal.set(Calendar.DAY_OF_MONTH, defaultCal.get(Calendar.DAY_OF_MONTH)); + 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)); + + Timestamp adjustedTimestamp = new Timestamp(targetCal.getTimeInMillis()); + adjustedTimestamp.setNanos(timestamp.getNanos()); + return adjustedTimestamp; + } + static BigQueryTypeCoercer INSTANCE; static { diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java index ab7fa7fb1c7a..d27d5070f6d7 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java @@ -18,10 +18,16 @@ 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 com.google.cloud.bigquery.QueryJobConfiguration; import com.google.cloud.bigquery.StandardSQLTypeName; import com.google.cloud.bigquery.jdbc.BigQueryParameterHandler.BigQueryStatementParameterType; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Calendar; +import java.util.TimeZone; import org.junit.jupiter.api.Test; public class BigQueryParameterHandlerTest { @@ -156,4 +162,34 @@ public void testConfigureParametersWidenNumericTypes() throws Exception { assertEquals("5", config.getPositionalParameters().get(0).getValue()); assertEquals("10", config.getPositionalParameters().get(1).getValue()); } + + @Test + public void testCalendarConversions() { + assertNull(BigQueryParameterHandler.convertDateWithCalendar(null, null)); + assertNull(BigQueryParameterHandler.convertTimeWithCalendar(null, null)); + assertNull(BigQueryParameterHandler.convertTimestampWithCalendar(null, null)); + + 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 + assertEquals(rawDate, BigQueryParameterHandler.convertDateWithCalendar(rawDate, null)); + assertEquals(rawTime, BigQueryParameterHandler.convertTimeWithCalendar(rawTime, null)); + assertEquals( + rawTimestamp, BigQueryParameterHandler.convertTimestampWithCalendar(rawTimestamp, null)); + + // UTC Calendar shifts wall-clock components into target timezone + Calendar utcCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + Date utcConvertedDate = BigQueryParameterHandler.convertDateWithCalendar(rawDate, utcCal); + assertNotNull(utcConvertedDate); + + Time utcConvertedTime = BigQueryParameterHandler.convertTimeWithCalendar(rawTime, utcCal); + assertNotNull(utcConvertedTime); + + Timestamp utcConvertedTimestamp = + BigQueryParameterHandler.convertTimestampWithCalendar(rawTimestamp, utcCal); + assertNotNull(utcConvertedTimestamp); + assertEquals(123456789, utcConvertedTimestamp.getNanos()); + } } From cfe22a7d61abbc339174a803bdb9b882ccbdc4ab Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Fri, 17 Jul 2026 13:31:44 -0400 Subject: [PATCH 09/16] refactor(bigquery-jdbc): remove legacy Calendar conversion wrappers from BigQueryParameterHandler and delegate directly to BigQueryTypeCoercionUtility --- .../jdbc/BigQueryParameterHandler.java | 20 ------------------- .../jdbc/BigQueryTypeCoercionUtility.java | 6 +++--- .../jdbc/BigQueryParameterHandlerTest.java | 18 ++++++++--------- 3 files changed, 12 insertions(+), 32 deletions(-) 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 c46ea697be6a..e54544994fc4 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 @@ -302,24 +302,4 @@ StandardSQLTypeName getSqlType(String name) { int getParametersArraySize() { return this.parametersArraySize; } - - /** - * Returns a safe, cloned copy of the provided Calendar to prevent mutating the caller's state - * during timezone conversions. If cal is null, returns a default Calendar instance. - */ - static Calendar getSafeCalendar(Calendar cal) { - return BigQueryTypeCoercionUtility.getSafeCalendar(cal); - } - - static Date convertDateWithCalendar(Date date, Calendar cal) { - return BigQueryTypeCoercionUtility.convertDateWithCalendar(date, cal); - } - - static Time convertTimeWithCalendar(Time time, Calendar cal) { - return BigQueryTypeCoercionUtility.convertTimeWithCalendar(time, cal); - } - - static Timestamp convertTimestampWithCalendar(Timestamp timestamp, Calendar cal) { - return BigQueryTypeCoercionUtility.convertTimestampWithCalendar(timestamp, cal); - } } 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 0df7950fbc4b..6b04466a0560 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 @@ -69,7 +69,7 @@ static Date convertDateWithCalendar(Date date, Calendar cal) { Calendar defaultCal = Calendar.getInstance(); defaultCal.setTime(date); - Calendar targetCal = (Calendar) cal.clone(); + Calendar targetCal = getSafeCalendar(cal); targetCal.set(Calendar.YEAR, defaultCal.get(Calendar.YEAR)); targetCal.set(Calendar.MONTH, defaultCal.get(Calendar.MONTH)); targetCal.set(Calendar.DAY_OF_MONTH, defaultCal.get(Calendar.DAY_OF_MONTH)); @@ -94,7 +94,7 @@ static Time convertTimeWithCalendar(Time time, Calendar cal) { Calendar defaultCal = Calendar.getInstance(); defaultCal.setTime(time); - Calendar targetCal = (Calendar) cal.clone(); + 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)); @@ -116,7 +116,7 @@ static Timestamp convertTimestampWithCalendar(Timestamp timestamp, Calendar cal) Calendar defaultCal = Calendar.getInstance(); defaultCal.setTime(timestamp); - Calendar targetCal = (Calendar) cal.clone(); + Calendar targetCal = getSafeCalendar(cal); targetCal.set(Calendar.YEAR, defaultCal.get(Calendar.YEAR)); targetCal.set(Calendar.MONTH, defaultCal.get(Calendar.MONTH)); targetCal.set(Calendar.DAY_OF_MONTH, defaultCal.get(Calendar.DAY_OF_MONTH)); diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java index d27d5070f6d7..0909dd38d28e 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java @@ -165,30 +165,30 @@ public void testConfigureParametersWidenNumericTypes() throws Exception { @Test public void testCalendarConversions() { - assertNull(BigQueryParameterHandler.convertDateWithCalendar(null, null)); - assertNull(BigQueryParameterHandler.convertTimeWithCalendar(null, null)); - assertNull(BigQueryParameterHandler.convertTimestampWithCalendar(null, null)); + assertNull(BigQueryTypeCoercionUtility.convertDateWithCalendar(null, null)); + assertNull(BigQueryTypeCoercionUtility.convertTimeWithCalendar(null, null)); + assertNull(BigQueryTypeCoercionUtility.convertTimestampWithCalendar(null, null)); 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 - assertEquals(rawDate, BigQueryParameterHandler.convertDateWithCalendar(rawDate, null)); - assertEquals(rawTime, BigQueryParameterHandler.convertTimeWithCalendar(rawTime, null)); + assertEquals(rawDate, BigQueryTypeCoercionUtility.convertDateWithCalendar(rawDate, null)); + assertEquals(rawTime, BigQueryTypeCoercionUtility.convertTimeWithCalendar(rawTime, null)); assertEquals( - rawTimestamp, BigQueryParameterHandler.convertTimestampWithCalendar(rawTimestamp, null)); + rawTimestamp, BigQueryTypeCoercionUtility.convertTimestampWithCalendar(rawTimestamp, null)); // UTC Calendar shifts wall-clock components into target timezone Calendar utcCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - Date utcConvertedDate = BigQueryParameterHandler.convertDateWithCalendar(rawDate, utcCal); + Date utcConvertedDate = BigQueryTypeCoercionUtility.convertDateWithCalendar(rawDate, utcCal); assertNotNull(utcConvertedDate); - Time utcConvertedTime = BigQueryParameterHandler.convertTimeWithCalendar(rawTime, utcCal); + Time utcConvertedTime = BigQueryTypeCoercionUtility.convertTimeWithCalendar(rawTime, utcCal); assertNotNull(utcConvertedTime); Timestamp utcConvertedTimestamp = - BigQueryParameterHandler.convertTimestampWithCalendar(rawTimestamp, utcCal); + BigQueryTypeCoercionUtility.convertTimestampWithCalendar(rawTimestamp, utcCal); assertNotNull(utcConvertedTimestamp); assertEquals(123456789, utcConvertedTimestamp.getNanos()); } From a6d0246ff0793ccb84dc0638eaefacbe70a74313 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Fri, 17 Jul 2026 13:39:23 -0400 Subject: [PATCH 10/16] test(bigquery-jdbc): relocate testCalendarConversions from BigQueryParameterHandlerTest to FieldValueTypeBigQueryCoercionUtilityTest --- .../jdbc/BigQueryParameterHandlerTest.java | 28 ---------------- ...dValueTypeBigQueryCoercionUtilityTest.java | 33 +++++++++++++++++++ 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java index 0909dd38d28e..7cd262d1a581 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java @@ -163,33 +163,5 @@ public void testConfigureParametersWidenNumericTypes() throws Exception { assertEquals("10", config.getPositionalParameters().get(1).getValue()); } - @Test - public void testCalendarConversions() { - assertNull(BigQueryTypeCoercionUtility.convertDateWithCalendar(null, null)); - assertNull(BigQueryTypeCoercionUtility.convertTimeWithCalendar(null, null)); - assertNull(BigQueryTypeCoercionUtility.convertTimestampWithCalendar(null, null)); - - 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 - assertEquals(rawDate, BigQueryTypeCoercionUtility.convertDateWithCalendar(rawDate, null)); - assertEquals(rawTime, BigQueryTypeCoercionUtility.convertTimeWithCalendar(rawTime, null)); - assertEquals( - rawTimestamp, BigQueryTypeCoercionUtility.convertTimestampWithCalendar(rawTimestamp, null)); - - // UTC Calendar shifts wall-clock components into target timezone - Calendar utcCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - Date utcConvertedDate = BigQueryTypeCoercionUtility.convertDateWithCalendar(rawDate, utcCal); - assertNotNull(utcConvertedDate); - - Time utcConvertedTime = BigQueryTypeCoercionUtility.convertTimeWithCalendar(rawTime, utcCal); - assertNotNull(utcConvertedTime); - - Timestamp utcConvertedTimestamp = - BigQueryTypeCoercionUtility.convertTimestampWithCalendar(rawTimestamp, utcCal); - assertNotNull(utcConvertedTimestamp); - assertEquals(123456789, utcConvertedTimestamp.getNanos()); } } 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); + } } From be03443b88675cb0932d8698ac5f3e6df389e40f Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Fri, 17 Jul 2026 13:40:08 -0400 Subject: [PATCH 11/16] fix(bigquery-jdbc): fix closing brace syntax error in BigQueryParameterHandlerTest --- .../cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java index 7cd262d1a581..2b475de7b8ce 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java @@ -162,6 +162,4 @@ public void testConfigureParametersWidenNumericTypes() throws Exception { assertEquals("5", config.getPositionalParameters().get(0).getValue()); assertEquals("10", config.getPositionalParameters().get(1).getValue()); } - - } } From d57664264432bcb2b66296c602c053b1c6ba2649 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Fri, 17 Jul 2026 13:47:51 -0400 Subject: [PATCH 12/16] perf(bigquery-jdbc): optimize Calendar date/time conversions using java.time APIs --- .../jdbc/BigQueryTypeCoercionUtility.java | 45 +++++-------------- 1 file changed, 11 insertions(+), 34 deletions(-) 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 6b04466a0560..15e221df19f5 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 @@ -33,6 +33,7 @@ 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; @@ -66,18 +67,9 @@ static Date convertDateWithCalendar(Date date, Calendar cal) { if (cal == null) { return date; } - Calendar defaultCal = Calendar.getInstance(); - defaultCal.setTime(date); - - Calendar targetCal = getSafeCalendar(cal); - targetCal.set(Calendar.YEAR, defaultCal.get(Calendar.YEAR)); - targetCal.set(Calendar.MONTH, defaultCal.get(Calendar.MONTH)); - targetCal.set(Calendar.DAY_OF_MONTH, defaultCal.get(Calendar.DAY_OF_MONTH)); - targetCal.set(Calendar.HOUR_OF_DAY, 0); - targetCal.set(Calendar.MINUTE, 0); - targetCal.set(Calendar.SECOND, 0); - targetCal.set(Calendar.MILLISECOND, 0); - return new Date(targetCal.getTimeInMillis()); + LocalDate localDate = date.toLocalDate(); + ZonedDateTime zdt = localDate.atStartOfDay(cal.getTimeZone().toZoneId()); + return new Date(zdt.toInstant().toEpochMilli()); } /** @@ -91,15 +83,10 @@ static Time convertTimeWithCalendar(Time time, Calendar cal) { if (cal == null) { 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()); + LocalTime localTime = time.toLocalTime(); + ZonedDateTime zdt = + localTime.atDate(LocalDate.of(1970, 1, 1)).atZone(cal.getTimeZone().toZoneId()); + return new Time(zdt.toInstant().toEpochMilli()); } /** @@ -113,19 +100,9 @@ static Timestamp convertTimestampWithCalendar(Timestamp timestamp, Calendar cal) if (cal == null) { return timestamp; } - Calendar defaultCal = Calendar.getInstance(); - defaultCal.setTime(timestamp); - - Calendar targetCal = getSafeCalendar(cal); - targetCal.set(Calendar.YEAR, defaultCal.get(Calendar.YEAR)); - targetCal.set(Calendar.MONTH, defaultCal.get(Calendar.MONTH)); - targetCal.set(Calendar.DAY_OF_MONTH, defaultCal.get(Calendar.DAY_OF_MONTH)); - 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)); - - Timestamp adjustedTimestamp = new Timestamp(targetCal.getTimeInMillis()); + LocalDateTime ldt = timestamp.toLocalDateTime(); + ZonedDateTime zdt = ldt.atZone(cal.getTimeZone().toZoneId()); + Timestamp adjustedTimestamp = Timestamp.from(zdt.toInstant()); adjustedTimestamp.setNanos(timestamp.getNanos()); return adjustedTimestamp; } From 4c177b3a0c222d0c383722de546d06655e45890a Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Fri, 17 Jul 2026 13:49:49 -0400 Subject: [PATCH 13/16] lint --- .../cloud/bigquery/jdbc/BigQueryParameterHandler.java | 3 --- .../cloud/bigquery/jdbc/BigQueryTypeCoercionUtility.java | 9 +++------ .../bigquery/jdbc/BigQueryParameterHandlerTest.java | 6 ------ .../jdbc/BigQueryPreparedStatementSettersTest.java | 6 +++++- 4 files changed, 8 insertions(+), 16 deletions(-) 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 e54544994fc4..026e68943cfc 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 @@ -22,10 +22,7 @@ import com.google.cloud.bigquery.exception.BigQueryJdbcException; import com.google.cloud.bigquery.exception.BigQueryJdbcSqlFeatureNotSupportedException; import java.math.BigInteger; -import java.sql.Date; import java.sql.SQLException; -import java.sql.Time; -import java.sql.Timestamp; import java.util.ArrayList; class BigQueryParameterHandler { 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 15e221df19f5..2e91aacba31b 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,7 +31,6 @@ 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; @@ -46,9 +45,7 @@ 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. - */ + /** 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(); @@ -73,8 +70,8 @@ static Date convertDateWithCalendar(Date date, Calendar cal) { } /** - * 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. + * 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) { diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java index 2b475de7b8ce..ab7fa7fb1c7a 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryParameterHandlerTest.java @@ -18,16 +18,10 @@ 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 com.google.cloud.bigquery.QueryJobConfiguration; import com.google.cloud.bigquery.StandardSQLTypeName; import com.google.cloud.bigquery.jdbc.BigQueryParameterHandler.BigQueryStatementParameterType; -import java.sql.Date; -import java.sql.Time; -import java.sql.Timestamp; -import java.util.Calendar; -import java.util.TimeZone; import org.junit.jupiter.api.Test; public class BigQueryParameterHandlerTest { 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 d1703f1f3f18..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 @@ -183,7 +183,11 @@ public void testGetParameterModeWithInOutParameters() throws Exception { preparedStatement.parameterHandler.setParameter( 2, null, Integer.class, BigQueryParameterHandler.BigQueryStatementParameterType.OUT, 0); preparedStatement.parameterHandler.setParameter( - 3, "inoutParam", String.class, BigQueryParameterHandler.BigQueryStatementParameterType.INOUT, 0); + 3, + "inoutParam", + String.class, + BigQueryParameterHandler.BigQueryStatementParameterType.INOUT, + 0); ParameterMetaData metaData = preparedStatement.getParameterMetaData(); assertEquals(ParameterMetaData.parameterModeIn, metaData.getParameterMode(1)); From bd0f6e4d3ca7bfbbd6bf9e89b4cb779eaa4e5ab6 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Fri, 17 Jul 2026 14:00:41 -0400 Subject: [PATCH 14/16] fix(bigquery-jdbc): update getSafeCalendar and convertTimeWithCalendar in BigQueryTypeCoercionUtility --- .../bigquery/jdbc/BigQueryTypeCoercionUtility.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) 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 2e91aacba31b..efad6e1f7887 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 @@ -80,10 +80,15 @@ static Time convertTimeWithCalendar(Time time, Calendar cal) { if (cal == null) { return time; } - LocalTime localTime = time.toLocalTime(); - ZonedDateTime zdt = - localTime.atDate(LocalDate.of(1970, 1, 1)).atZone(cal.getTimeZone().toZoneId()); - return new Time(zdt.toInstant().toEpochMilli()); + 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()); } /** From 00db9bd5e74ffb2f9616b175e3c1a013f4d9c0ac Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Fri, 17 Jul 2026 14:50:52 -0400 Subject: [PATCH 15/16] fix(bigquery-jdbc): refine temporal timezone coercion for Calendar getters and setters --- .../jdbc/BigQueryTypeCoercionUtility.java | 43 ++++++++++++++----- 1 file changed, 32 insertions(+), 11 deletions(-) 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 efad6e1f7887..490a55a96f6a 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,6 +31,7 @@ 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; @@ -58,14 +59,30 @@ static Calendar getSafeCalendar(Calendar cal) { * Calendar's timezone per JDBC specification. */ static Date convertDateWithCalendar(Date date, Calendar cal) { - if (date == null) { - return null; + if (date == null || cal == null) { + return date; } - if (cal == null) { + ZoneId systemZone = ZoneId.systemDefault(); + ZoneId targetZone = cal.getTimeZone().toZoneId(); + if (systemZone.equals(targetZone)) { return date; } LocalDate localDate = date.toLocalDate(); - ZonedDateTime zdt = localDate.atStartOfDay(cal.getTimeZone().toZoneId()); + 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()); } @@ -74,10 +91,12 @@ static Date convertDateWithCalendar(Date date, Calendar cal) { * fields into the target Calendar's timezone per JDBC specification. */ static Time convertTimeWithCalendar(Time time, Calendar cal) { - if (time == null) { - return null; + if (time == null || cal == null) { + return time; } - if (cal == null) { + ZoneId systemZone = ZoneId.systemDefault(); + ZoneId targetZone = cal.getTimeZone().toZoneId(); + if (systemZone.equals(targetZone)) { return time; } Calendar defaultCal = Calendar.getInstance(); @@ -96,14 +115,16 @@ static Time convertTimeWithCalendar(Time time, Calendar cal) { * timezone per JDBC specification while preserving nanosecond precision. */ static Timestamp convertTimestampWithCalendar(Timestamp timestamp, Calendar cal) { - if (timestamp == null) { - return null; + if (timestamp == null || cal == null) { + return timestamp; } - if (cal == null) { + ZoneId systemZone = ZoneId.systemDefault(); + ZoneId targetZone = cal.getTimeZone().toZoneId(); + if (systemZone.equals(targetZone)) { return timestamp; } LocalDateTime ldt = timestamp.toLocalDateTime(); - ZonedDateTime zdt = ldt.atZone(cal.getTimeZone().toZoneId()); + ZonedDateTime zdt = ldt.atZone(targetZone); Timestamp adjustedTimestamp = Timestamp.from(zdt.toInstant()); adjustedTimestamp.setNanos(timestamp.getNanos()); return adjustedTimestamp; From ffa1ed378862c3c255341e5141e4bdcff835f27d Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Fri, 17 Jul 2026 16:01:06 -0400 Subject: [PATCH 16/16] defensive getSafeCalendar --- .../bigquery/jdbc/BigQueryTypeCoercionUtility.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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 490a55a96f6a..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 @@ -51,7 +51,15 @@ static Calendar getSafeCalendar(Calendar cal) { if (cal == null) { return Calendar.getInstance(); } - return (Calendar) cal.clone(); + 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; } /**