From fa2a61cbc1cbae00c96f1ce3a6c78bb0ea225cb1 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Tue, 1 Sep 2026 14:30:13 -0400 Subject: [PATCH 1/2] chore(bigquery-jdbc): clean up old type coercion files --- .../bigquery/jdbc/BigQueryBaseResultSet.java | 6 +- .../jdbc/BigQueryCallableStatement.java | 18 +- .../cloud/bigquery/jdbc/BigQueryCoercion.java | 44 -- .../jdbc/BigQueryDefaultCoercions.java | 102 ---- .../jdbc/BigQueryJdbcTypeMappings.java | 245 -------- .../jdbc/BigQueryPreparedStatement.java | 6 +- .../jdbc/BigQueryTemporalUtility.java | 108 ++++ .../bigquery/jdbc/BigQueryTypeCoercer.java | 155 ----- .../jdbc/BigQueryTypeCoercerBuilder.java | 79 --- .../jdbc/BigQueryTypeCoercionUtility.java | 572 ------------------ ...FormatTypeBigQueryCoercionUtilityTest.java | 255 -------- .../jdbc/BigQueryDefaultCoercionsTest.java | 242 -------- .../jdbc/BigQueryTypeCoercerBuilderTest.java | 43 -- .../jdbc/BigQueryTypeCoercerTest.java | 100 --- ...dValueTypeBigQueryCoercionUtilityTest.java | 429 ------------- .../cloud/bigquery/jdbc/NullHandlingTest.java | 44 -- 16 files changed, 123 insertions(+), 2325 deletions(-) delete mode 100644 java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCoercion.java delete mode 100644 java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDefaultCoercions.java delete mode 100644 java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcTypeMappings.java delete mode 100644 java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercer.java delete mode 100644 java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercerBuilder.java delete mode 100644 java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercionUtility.java delete mode 100644 java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/ArrowFormatTypeBigQueryCoercionUtilityTest.java delete mode 100644 java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryDefaultCoercionsTest.java delete mode 100644 java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercerBuilderTest.java delete mode 100644 java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercerTest.java delete mode 100644 java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/FieldValueTypeBigQueryCoercionUtilityTest.java delete mode 100644 java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/NullHandlingTest.java diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseResultSet.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseResultSet.java index 70000e749b24..f20e337265b8 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseResultSet.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseResultSet.java @@ -552,21 +552,21 @@ public InputStream getBinaryStream(int columnIndex) throws SQLException { public Date getDate(int columnIndex, Calendar cal) throws SQLException { LOG.finestTrace("getDate"); Date date = getDate(columnIndex); - return BigQueryTypeCoercionUtility.convertDateWithCalendar(date, cal); + return BigQueryTemporalUtility.convertDateWithCalendar(date, cal); } @Override public Time getTime(int columnIndex, Calendar cal) throws SQLException { LOG.finestTrace("getTime"); Time time = getTime(columnIndex); - return BigQueryTypeCoercionUtility.convertTimeWithCalendar(time, cal); + return BigQueryTemporalUtility.convertTimeWithCalendar(time, cal); } @Override public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException { LOG.finestTrace("getTimestamp"); Timestamp timestamp = getTimestamp(columnIndex); - return BigQueryTypeCoercionUtility.convertTimestampWithCalendar(timestamp, cal); + return BigQueryTemporalUtility.convertTimestampWithCalendar(timestamp, cal); } @Override diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCallableStatement.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCallableStatement.java index 964ffab74aba..51599c154cf6 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCallableStatement.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCallableStatement.java @@ -180,13 +180,13 @@ public Date getDate(String parameterName) throws SQLException { @Override public Date getDate(int parameterIndex, Calendar calendar) throws SQLException { Date date = getDate(parameterIndex); - return BigQueryTypeCoercionUtility.convertDateWithCalendar(date, calendar); + return BigQueryTemporalUtility.convertDateWithCalendar(date, calendar); } @Override public Date getDate(String parameterName, Calendar calendar) throws SQLException { Date date = getDate(parameterName); - return BigQueryTypeCoercionUtility.convertDateWithCalendar(date, calendar); + return BigQueryTemporalUtility.convertDateWithCalendar(date, calendar); } @Override @@ -459,13 +459,13 @@ public Time getTime(String parameterName) throws SQLException { @Override public Time getTime(int parameterIndex, Calendar calendar) throws SQLException { Time time = getTime(parameterIndex); - return BigQueryTypeCoercionUtility.convertTimeWithCalendar(time, calendar); + return BigQueryTemporalUtility.convertTimeWithCalendar(time, calendar); } @Override public Time getTime(String parameterName, Calendar calendar) throws SQLException { Time time = getTime(parameterName); - return BigQueryTypeCoercionUtility.convertTimeWithCalendar(time, calendar); + return BigQueryTemporalUtility.convertTimeWithCalendar(time, calendar); } @Override @@ -481,13 +481,13 @@ public Timestamp getTimestamp(String parameterName) throws SQLException { @Override public Timestamp getTimestamp(int parameterIndex, Calendar calendar) throws SQLException { Timestamp ts = getTimestamp(parameterIndex); - return BigQueryTypeCoercionUtility.convertTimestampWithCalendar(ts, calendar); + return BigQueryTemporalUtility.convertTimestampWithCalendar(ts, calendar); } @Override public Timestamp getTimestamp(String parameterName, Calendar calendar) throws SQLException { Timestamp ts = getTimestamp(parameterName); - return BigQueryTypeCoercionUtility.convertTimestampWithCalendar(ts, calendar); + return BigQueryTemporalUtility.convertTimestampWithCalendar(ts, calendar); } @Override @@ -708,7 +708,7 @@ public void setDate(String parameterName, Date value, Calendar calendar) throws checkClosed(); this.parameterHandler.setParameter( parameterName, - BigQueryTypeCoercionUtility.convertDateToCalendar(value, calendar), + BigQueryTemporalUtility.convertDateToCalendar(value, calendar), Date.class, BigQueryStatementParameterType.IN, 0); @@ -860,7 +860,7 @@ public void setTime(String parameterName, Time value, Calendar calendar) throws checkClosed(); this.parameterHandler.setParameter( parameterName, - value == null ? null : BigQueryTypeCoercionUtility.convertTimeWithCalendar(value, calendar), + value == null ? null : BigQueryTemporalUtility.convertTimeWithCalendar(value, calendar), Time.class, BigQueryStatementParameterType.IN, 0); @@ -879,7 +879,7 @@ public void setTimestamp(String parameterName, Timestamp value, Calendar calenda checkClosed(); this.parameterHandler.setParameter( parameterName, - BigQueryTypeCoercionUtility.convertTimestampWithCalendar(value, calendar), + BigQueryTemporalUtility.convertTimestampWithCalendar(value, calendar), Timestamp.class, BigQueryStatementParameterType.IN, 0); diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCoercion.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCoercion.java deleted file mode 100644 index 6265af0decc7..000000000000 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryCoercion.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2023 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.api.core.InternalApi; -import java.util.function.Function; - -/** - * A {@link BigQueryCoercion} is responsible for coercing one type to another. An implementation of - * {@link BigQueryCoercion} is used to extend the behaviour of {@link BigQueryTypeCoercer} for the - * coercion of one user defined type to another. - * - * @param represents the source type - * @param represents the target type - */ -@InternalApi -interface BigQueryCoercion extends Function { - /** - * Coerce the provided value to the desired type. - * - * @param value the input value. - * @return the output value after coercion. - */ - OUTPUT coerce(INPUT value); - - @Override - default OUTPUT apply(INPUT input) { - return coerce(input); - } -} diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDefaultCoercions.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDefaultCoercions.java deleted file mode 100644 index 324888982a44..000000000000 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDefaultCoercions.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2023 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.api.core.InternalApi; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.math.RoundingMode; - -/** A registry of all the inbuilt {@link BigQueryCoercion}s that framework offers by default. */ -@InternalApi -class BigQueryDefaultCoercions { - - /** - * Creates a {@link BigQueryTypeCoercerBuilder} with all the inbuilt {@link BigQueryCoercion}s. - *
  • {@link BigQueryTypeCoercer#INSTANCE} uses this builder to populate itself with all the - * default {@link BigQueryCoercion}s. - *
  • A {@link BigQueryTypeCoercerBuilder} created through {@link BigQueryTypeCoercer#builder()} - * method also contains all these default {@link BigQueryCoercion}s - */ - static BigQueryTypeCoercerBuilder builder() { - BigQueryTypeCoercerBuilder builder = new BigQueryTypeCoercerBuilder(); - - // TODO: can we figure out the class parameters from coercion itself? - builder.registerTypeCoercion( - (String s) -> s != null && ("1".equals(s.trim()) || Boolean.parseBoolean(s)), - String.class, - Boolean.class); - builder.registerTypeCoercion(Integer::parseInt, String.class, Integer.class); - builder.registerTypeCoercion(BigInteger::new, String.class, BigInteger.class); - builder.registerTypeCoercion(Long::valueOf, String.class, Long.class); - builder.registerTypeCoercion(Double::valueOf, String.class, Double.class); - builder.registerTypeCoercion(BigDecimal::new, String.class, BigDecimal.class); - - builder.registerTypeCoercion((b) -> b ? 1 : 0, Boolean.class, Integer.class); - - builder.registerTypeCoercion(Integer::intValue, Integer.class, Integer.class); - builder.registerTypeCoercion(Integer::shortValue, Integer.class, Short.class); - builder.registerTypeCoercion(Integer::byteValue, Integer.class, Byte.class); - builder.registerTypeCoercion(Integer::doubleValue, Integer.class, Double.class); - builder.registerTypeCoercion(Integer::floatValue, Integer.class, Float.class); - - builder.registerTypeCoercion(Long::intValue, Long.class, Integer.class); - builder.registerTypeCoercion(Long::shortValue, Long.class, Short.class); - builder.registerTypeCoercion(Long::byteValue, Long.class, Byte.class); - builder.registerTypeCoercion(Long::doubleValue, Long.class, Double.class); - builder.registerTypeCoercion(Long::floatValue, Long.class, Float.class); - - builder.registerTypeCoercion(Double::floatValue, Double.class, Float.class); - builder.registerTypeCoercion(Double::longValue, Double.class, Long.class); - builder.registerTypeCoercion(Double::intValue, Double.class, Integer.class); - builder.registerTypeCoercion(Double::shortValue, Double.class, Short.class); - builder.registerTypeCoercion(Double::byteValue, Double.class, Byte.class); - builder.registerTypeCoercion(BigDecimal::valueOf, Double.class, BigDecimal.class); - - builder.registerTypeCoercion(Float::intValue, Float.class, Integer.class); - builder.registerTypeCoercion(Float::byteValue, Float.class, Byte.class); - builder.registerTypeCoercion(Float::shortValue, Float.class, Short.class); - builder.registerTypeCoercion(Float::doubleValue, Float.class, Double.class); - - builder.registerTypeCoercion(BigInteger::longValue, BigInteger.class, Long.class); - builder.registerTypeCoercion(BigDecimal::new, BigInteger.class, BigDecimal.class); - - builder.registerTypeCoercion(BigDecimal::doubleValue, BigDecimal.class, Double.class); - builder.registerTypeCoercion(BigDecimal::toBigInteger, BigDecimal.class, BigInteger.class); - builder.registerTypeCoercion( - bigDecimal -> bigDecimal.setScale(0, RoundingMode.DOWN).intValueExact(), - BigDecimal.class, - Integer.class); - builder.registerTypeCoercion( - bigDecimal -> bigDecimal.setScale(0, RoundingMode.DOWN).longValueExact(), - BigDecimal.class, - Long.class); - builder.registerTypeCoercion( - bigDecimal -> bigDecimal.setScale(0, RoundingMode.DOWN).shortValueExact(), - BigDecimal.class, - Short.class); - builder.registerTypeCoercion( - bigDecimal -> bigDecimal.setScale(0, RoundingMode.DOWN).byteValueExact(), - BigDecimal.class, - Byte.class); - builder.registerTypeCoercion(BigDecimal::floatValue, BigDecimal.class, Float.class); - - builder.registerTypeCoercion(unused -> false, Void.class, Boolean.class); - - return builder; - } -} 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 deleted file mode 100644 index 913cfb181bed..000000000000 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcTypeMappings.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright 2023 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.api.core.InternalApi; -import com.google.cloud.bigquery.StandardSQLTypeName; -import com.google.cloud.bigquery.exception.BigQueryJdbcSqlFeatureNotSupportedException; -import com.google.common.collect.ImmutableMap; -import com.google.gson.JsonObject; -import java.math.BigDecimal; -import java.sql.Array; -import java.sql.Date; -import java.sql.Struct; -import java.sql.Time; -import java.sql.Timestamp; -import java.sql.Types; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.OffsetDateTime; -import java.time.ZonedDateTime; -import java.util.AbstractMap.SimpleEntry; -import java.util.Map; - -@InternalApi -class BigQueryJdbcTypeMappings { - private static final BigQueryJdbcCustomLogger LOG = - new BigQueryJdbcCustomLogger(BigQueryJdbcTypeMappings.class.getName()); - - static final Map> standardSQLToJavaTypeMapping = - ImmutableMap.ofEntries( - entry(StandardSQLTypeName.INT64, Long.class), - entry(StandardSQLTypeName.BOOL, Boolean.class), - entry(StandardSQLTypeName.FLOAT64, Double.class), - entry(StandardSQLTypeName.NUMERIC, BigDecimal.class), - entry(StandardSQLTypeName.BIGNUMERIC, BigDecimal.class), - entry(StandardSQLTypeName.STRING, String.class), - entry(StandardSQLTypeName.TIMESTAMP, Timestamp.class), - entry(StandardSQLTypeName.DATE, Date.class), - entry(StandardSQLTypeName.TIME, Time.class), - entry(StandardSQLTypeName.DATETIME, Timestamp.class), - entry(StandardSQLTypeName.GEOGRAPHY, String.class), - entry(StandardSQLTypeName.JSON, String.class), - entry(StandardSQLTypeName.INTERVAL, String.class), - entry(StandardSQLTypeName.RANGE, String.class), - entry(StandardSQLTypeName.BYTES, byte[].class), - entry(StandardSQLTypeName.STRUCT, Struct.class), - entry(StandardSQLTypeName.ARRAY, Array.class)); - - static final Map standardSQLToJavaSqlTypesMapping = - ImmutableMap.ofEntries( - entry(StandardSQLTypeName.INT64, Types.BIGINT), - entry(StandardSQLTypeName.BOOL, Types.BOOLEAN), - entry(StandardSQLTypeName.FLOAT64, Types.DOUBLE), - entry(StandardSQLTypeName.NUMERIC, Types.NUMERIC), - entry(StandardSQLTypeName.BIGNUMERIC, Types.NUMERIC), - entry(StandardSQLTypeName.STRING, Types.NVARCHAR), - entry(StandardSQLTypeName.TIMESTAMP, Types.TIMESTAMP), - entry(StandardSQLTypeName.DATE, Types.DATE), - entry(StandardSQLTypeName.TIME, Types.TIME), - entry(StandardSQLTypeName.DATETIME, Types.TIMESTAMP), - entry(StandardSQLTypeName.GEOGRAPHY, Types.OTHER), - entry(StandardSQLTypeName.JSON, Types.OTHER), - entry(StandardSQLTypeName.INTERVAL, Types.OTHER), - entry(StandardSQLTypeName.RANGE, Types.OTHER), - entry(StandardSQLTypeName.BYTES, Types.VARBINARY), - entry(StandardSQLTypeName.STRUCT, Types.STRUCT), - entry(StandardSQLTypeName.ARRAY, Types.ARRAY)); - - static final Map> javaSQLToJavaTypeMapping = - ImmutableMap.ofEntries( - entry(Types.BIGINT, Long.class), - entry(Types.INTEGER, Integer.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), - entry(Types.NUMERIC, BigDecimal.class), - entry(Types.VARCHAR, String.class), - entry(Types.NVARCHAR, String.class), - entry(Types.TIMESTAMP, Timestamp.class), - entry(Types.DATE, Date.class), - entry(Types.TIME, Time.class), - entry(Types.OTHER, String.class), - entry(Types.BINARY, byte[].class), - entry(Types.VARBINARY, byte[].class), - entry(Types.STRUCT, Struct.class), - entry(Types.BIT, Boolean.class), - entry(Types.ARRAY, Array.class), - entry(Types.NULL, String.class)); - - static StandardSQLTypeName classToType(Class type) - throws BigQueryJdbcSqlFeatureNotSupportedException { - if (Boolean.class.isAssignableFrom(type)) { - return StandardSQLTypeName.BOOL; - } - if (String.class.isAssignableFrom(type)) { - return StandardSQLTypeName.STRING; - } - if (Integer.class.isAssignableFrom(type)) { - return StandardSQLTypeName.INT64; - } - if (Long.class.isAssignableFrom(type)) { - return StandardSQLTypeName.INT64; - } - if (Short.class.isAssignableFrom(type)) { - return StandardSQLTypeName.INT64; - } - if (Double.class.isAssignableFrom(type)) { - return StandardSQLTypeName.FLOAT64; - } - if (Float.class.isAssignableFrom(type)) { - return StandardSQLTypeName.FLOAT64; - } - if (BigDecimal.class.isAssignableFrom(type)) { - return StandardSQLTypeName.NUMERIC; - } - if (Date.class.isAssignableFrom(type) || LocalDate.class.isAssignableFrom(type)) { - return StandardSQLTypeName.DATE; - } - if (LocalDateTime.class.isAssignableFrom(type)) { - return StandardSQLTypeName.DATETIME; - } - if (Timestamp.class.isAssignableFrom(type) - || OffsetDateTime.class.isAssignableFrom(type) - || Instant.class.isAssignableFrom(type) - || ZonedDateTime.class.isAssignableFrom(type)) { - return StandardSQLTypeName.TIMESTAMP; - } - if (Time.class.isAssignableFrom(type) || LocalTime.class.isAssignableFrom(type)) { - return StandardSQLTypeName.TIME; - } - if (JsonObject.class.isAssignableFrom(type)) { - return StandardSQLTypeName.JSON; - } - if (Byte.class.isAssignableFrom(type)) { - return StandardSQLTypeName.INT64; - } - if (Array.class.isAssignableFrom(type)) { - return StandardSQLTypeName.ARRAY; - } - if (Struct.class.isAssignableFrom(type)) { - return StandardSQLTypeName.STRUCT; - } - if (byte[].class.isAssignableFrom(type)) { - return StandardSQLTypeName.BYTES; - } - throw new BigQueryJdbcSqlFeatureNotSupportedException( - "Unsupported object type for QueryParameter: " + type); - } - - static Class getJavaType(int javaSQLType) throws BigQueryJdbcSqlFeatureNotSupportedException { - if (!javaSQLToJavaTypeMapping.containsKey(javaSQLType)) { - throw new BigQueryJdbcSqlFeatureNotSupportedException( - "Unsupported Java type for SQL type: " + javaSQLType); - } - Class javaType = javaSQLToJavaTypeMapping.get(javaSQLType); - if (javaType == null) { - // This should never happen unless the map was initialized with null values. - throw new BigQueryJdbcSqlFeatureNotSupportedException( - "Unsupported Java type for SQL type: " + javaSQLType); - } - return javaType; - } - - private static SimpleEntry entry(K key, V value) { - return new SimpleEntry<>(key, value); - } - - static class ColumnTypeInfo { - final int jdbcType; - final String typeName; - final Integer columnSize; - final Integer decimalDigits; - final Integer numPrecRadix; - - ColumnTypeInfo( - int jdbcType, - String typeName, - Integer columnSize, - Integer decimalDigits, - Integer numPrecRadix) { - this.jdbcType = jdbcType; - this.typeName = typeName; - this.columnSize = columnSize; - this.decimalDigits = decimalDigits; - this.numPrecRadix = numPrecRadix; - } - } - - static final Map STANDARD_TYPE_INFO = - ImmutableMap.builder() - .put(StandardSQLTypeName.INT64, new ColumnTypeInfo(Types.BIGINT, "INT64", 19, 0, 10)) - .put(StandardSQLTypeName.BOOL, new ColumnTypeInfo(Types.BOOLEAN, "BOOL", 1, null, null)) - .put( - StandardSQLTypeName.FLOAT64, - new ColumnTypeInfo(Types.DOUBLE, "FLOAT64", 15, null, 10)) - .put(StandardSQLTypeName.NUMERIC, new ColumnTypeInfo(Types.NUMERIC, "NUMERIC", 38, 9, 10)) - .put( - StandardSQLTypeName.BIGNUMERIC, - new ColumnTypeInfo(Types.NUMERIC, "BIGNUMERIC", 77, 38, 10)) - .put( - StandardSQLTypeName.STRING, - new ColumnTypeInfo(Types.NVARCHAR, "STRING", null, null, null)) - .put( - StandardSQLTypeName.TIMESTAMP, - new ColumnTypeInfo(Types.TIMESTAMP, "TIMESTAMP", 26, 6, null)) - .put( - StandardSQLTypeName.DATETIME, - new ColumnTypeInfo(Types.TIMESTAMP, "DATETIME", 26, 6, null)) - .put(StandardSQLTypeName.DATE, new ColumnTypeInfo(Types.DATE, "DATE", 10, 0, null)) - .put(StandardSQLTypeName.TIME, new ColumnTypeInfo(Types.TIME, "TIME", 15, 6, null)) - .put( - StandardSQLTypeName.GEOGRAPHY, - new ColumnTypeInfo(Types.OTHER, "GEOGRAPHY", null, null, null)) - .put(StandardSQLTypeName.JSON, new ColumnTypeInfo(Types.OTHER, "JSON", null, null, null)) - .put( - StandardSQLTypeName.INTERVAL, - new ColumnTypeInfo(Types.OTHER, "INTERVAL", null, null, null)) - .put( - StandardSQLTypeName.RANGE, new ColumnTypeInfo(Types.OTHER, "RANGE", null, null, null)) - .put( - StandardSQLTypeName.BYTES, - new ColumnTypeInfo(Types.VARBINARY, "BYTES", null, null, null)) - .put( - StandardSQLTypeName.STRUCT, - new ColumnTypeInfo(Types.STRUCT, "STRUCT", null, null, null)) - .build(); -} 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 a5b40a8e2d34..7ba6af89c7b6 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 @@ -532,13 +532,13 @@ public ResultSetMetaData getMetaData() throws SQLException { @Override public void setDate(int parameterIndex, Date value, Calendar calendar) throws SQLException { checkClosed(); - setDate(parameterIndex, BigQueryTypeCoercionUtility.convertDateToCalendar(value, calendar)); + setDate(parameterIndex, BigQueryTemporalUtility.convertDateToCalendar(value, calendar)); } @Override public void setTime(int parameterIndex, Time value, Calendar calendar) throws SQLException { checkClosed(); - setTime(parameterIndex, BigQueryTypeCoercionUtility.convertTimeWithCalendar(value, calendar)); + setTime(parameterIndex, BigQueryTemporalUtility.convertTimeWithCalendar(value, calendar)); } @Override @@ -546,7 +546,7 @@ public void setTimestamp(int parameterIndex, Timestamp value, Calendar calendar) throws SQLException { checkClosed(); setTimestamp( - parameterIndex, BigQueryTypeCoercionUtility.convertTimestampWithCalendar(value, calendar)); + parameterIndex, BigQueryTemporalUtility.convertTimestampWithCalendar(value, calendar)); } @Override diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTemporalUtility.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTemporalUtility.java index db7912ff4b6a..5cfb4ff806fd 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTemporalUtility.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTemporalUtility.java @@ -25,6 +25,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.Calendar; /** @@ -149,4 +150,111 @@ public static long getLocalMillis(long millisOfDay, ZoneId zoneId) { .toInstant() .toEpochMilli(); } + + /** Returns a defensively cloned Calendar instance or a new default Calendar if input is null. */ + static Calendar getSafeCalendar(Calendar cal) { + if (cal == null) { + return Calendar.getInstance(); + } + Object cloned = cal.clone(); + if (cloned instanceof Calendar) { + return (Calendar) cloned; + } + Calendar safeCal = Calendar.getInstance(); + if (cal.getTimeZone() != null) { + safeCal.setTimeZone(cal.getTimeZone()); + } + return safeCal; + } + + /** + * Converts a {@link Date} for reading/outbound operations (e.g., {@code getDate(..., Calendar)}) + * by shifting its wall-clock year, month, and day fields into the target {@link Calendar}'s + * timezone per the JDBC specification. + * + * @param date the date in system-default time representation + * @param cal the target Calendar containing the desired timezone + * @return the adjusted Date starting at 00:00:00 in the target Calendar's timezone + */ + static Date convertDateWithCalendar(Date date, Calendar cal) { + if (date == null || cal == null) { + return date; + } + ZoneId systemZone = ZoneId.systemDefault(); + ZoneId targetZone = cal.getTimeZone().toZoneId(); + if (systemZone.equals(targetZone)) { + return date; + } + LocalDate localDate = date.toLocalDate(); + ZonedDateTime zdt = localDate.atStartOfDay(targetZone); + return new Date(zdt.toInstant().toEpochMilli()); + } + + /** + * Converts a {@link Date} for writing/inbound parameter setting operations (e.g., {@code + * setDate(..., Calendar)}) by extracting its local date fields as interpreted in the target + * {@link Calendar}'s timezone and normalizing them back into start-of-day in the system-default + * timezone. + * + * @param date the date instant specified relative to the target Calendar + * @param cal the Calendar containing the source timezone + * @return the normalized Date starting at 00:00:00 in the system-default timezone + */ + static Date convertDateToCalendar(Date date, Calendar cal) { + if (date == null || cal == null) { + return date; + } + ZoneId systemZone = ZoneId.systemDefault(); + ZoneId targetZone = cal.getTimeZone().toZoneId(); + if (systemZone.equals(targetZone)) { + return date; + } + LocalDate localDate = Instant.ofEpochMilli(date.getTime()).atZone(targetZone).toLocalDate(); + ZonedDateTime zdt = localDate.atStartOfDay(systemZone); + return new Date(zdt.toInstant().toEpochMilli()); + } + + /** + * Converts a java.sql.Time by shifting its wall-clock hour, minute, second, and millisecond + * fields into the target Calendar's timezone per JDBC specification. + */ + static Time convertTimeWithCalendar(Time time, Calendar cal) { + if (time == null || cal == null) { + return time; + } + ZoneId systemZone = ZoneId.systemDefault(); + ZoneId targetZone = cal.getTimeZone().toZoneId(); + if (systemZone.equals(targetZone)) { + return time; + } + Calendar defaultCal = Calendar.getInstance(); + defaultCal.setTime(time); + + Calendar targetCal = getSafeCalendar(cal); + targetCal.set(Calendar.HOUR_OF_DAY, defaultCal.get(Calendar.HOUR_OF_DAY)); + targetCal.set(Calendar.MINUTE, defaultCal.get(Calendar.MINUTE)); + targetCal.set(Calendar.SECOND, defaultCal.get(Calendar.SECOND)); + targetCal.set(Calendar.MILLISECOND, defaultCal.get(Calendar.MILLISECOND)); + return new Time(targetCal.getTimeInMillis()); + } + + /** + * Converts a java.sql.Timestamp by shifting its wall-clock fields into the target Calendar's + * timezone per JDBC specification while preserving nanosecond precision. + */ + static Timestamp convertTimestampWithCalendar(Timestamp timestamp, Calendar cal) { + if (timestamp == null || cal == null) { + return timestamp; + } + ZoneId systemZone = ZoneId.systemDefault(); + ZoneId targetZone = cal.getTimeZone().toZoneId(); + if (systemZone.equals(targetZone)) { + return timestamp; + } + LocalDateTime ldt = timestamp.toLocalDateTime(); + ZonedDateTime zdt = ldt.atZone(targetZone); + Timestamp adjustedTimestamp = Timestamp.from(zdt.toInstant()); + adjustedTimestamp.setNanos(timestamp.getNanos()); + return adjustedTimestamp; + } } diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercer.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercer.java deleted file mode 100644 index 9f968fd4b8d0..000000000000 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercer.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2023 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.api.core.InternalApi; -import com.google.cloud.bigquery.FieldValue; -import com.google.cloud.bigquery.exception.BigQueryJdbcCoercionException; -import com.google.cloud.bigquery.exception.BigQueryJdbcCoercionNotFoundException; -import java.util.Map; - -/** - * Provides a declarative mechanism for coercing an object from one type to another. For example, - * coercion of {@link String} to {@link Integer} can be achieved like this: - * - *
    - *   Integer value = BigQueryTypeCoercer.INSTANCE.coerceTo(Integer.class, "3452148");
    - *   System.out.println(value); // 3452148
    - * 
    - * - * A {@link BigQueryTypeCoercer} is baked with all the default {@link BigQueryCoercion}s from {@link - * BigQueryDefaultCoercions} to coerce all the primitive types. - * - *

    It is also possible to extend the behaviour of {@link BigQueryTypeCoercer} to other custom - * user defined types by creating an implementation of {@link BigQueryCoercion} and register it with - * {@link BigQueryTypeCoercerBuilder} using it's {@link - * BigQueryTypeCoercerBuilder#registerTypeCoercion(BigQueryCoercion)} method. - * - *

    - *   public class TextToStringCoercion extends BigQueryBigQueryCoercion{
    - *
    - *    public TextToStringCoercion() {
    - *       super(Text.class, String.class);
    - *    }
    - *
    - *    @Override
    - *    String coerce(Text text) {
    - *       return text.toString();  // logic to coerce from Text type to String type
    - *    }
    - *  }
    - * 
    - * - * and use it like this - * - *
    - *    byte[] bytesArray = {72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33};
    - *    Text text = new Text(bytesArray);
    - *
    - *    BigQueryTypeCoercer typeCoercer = new BigQueryTypeCoercerBuilder()
    - *         .registerCoercion(new TextToStringCoercion())  // registering a custom coercion
    - *         .build();
    - *    System.out.println(typeCoercer.coerceTo(String.class, text));  //  Hello World!
    - * 
    - */ -@InternalApi -class BigQueryTypeCoercer { - private static final BigQueryJdbcResultSetLogger LOG = - BigQueryJdbcResultSetLogger.getLogger(BigQueryTypeCoercer.class); - - /** A {@link BigQueryTypeCoercer} instance with all the inbuilt {@link BigQueryCoercion}s */ - static BigQueryTypeCoercer INSTANCE; - - static { - INSTANCE = BigQueryDefaultCoercions.builder().build(); - } - - private final Map, Map, BigQueryCoercion>> allCoercions; - - BigQueryTypeCoercer(Map, Map, BigQueryCoercion>> allCoercions) { - this.allCoercions = allCoercions; - } - - /** - * Coerce an object to the type specified. - * - * @param value the object that needs to be coerced. - * @param targetClass the target class for the coercion - * @throws BigQueryJdbcCoercionNotFoundException when coercion can not be performed to the target - * type. - * @throws BigQueryJdbcCoercionException when an error is encountered while performing the - * coercion. - */ - T coerceTo(Class targetClass, Object value) { - return coerceTo(targetClass, value, null); - } - - T coerceTo(Class targetClass, Object value, BigQueryJdbcResultSetLogger log) { - Class sourceClass = value == null ? Void.class : value.getClass(); - // FieldValue object for null-values requires special check - if (sourceClass == FieldValue.class && ((FieldValue.class.cast(value)).isNull())) { - sourceClass = Void.class; - } - // No coercion needed - if (sourceClass.equals(targetClass)) { - return targetClass.cast(value); - } - BigQueryCoercion coercion = findCoercion(sourceClass, targetClass); - BigQueryJdbcResultSetLogger effectiveLog = log != null ? log : LOG; - effectiveLog.finestTrace( - "coerceTo", () -> String.format("%s coercion for %s", coercion, value)); - // Value is null case & no explicit coercion - if (sourceClass == Void.class && coercion == null) { - return null; - } - if (coercion == null) { - if (targetClass.equals(String.class)) { - return (T) value.toString(); - } - throw new BigQueryJdbcCoercionNotFoundException(sourceClass, targetClass); - } - try { - return coercion.coerce(sourceClass != Void.class ? value : null); - } catch (Exception ex) { - throw new BigQueryJdbcCoercionException(ex); - } - } - - /** - * Creates a {@link BigQueryTypeCoercerBuilder} with all the default coercions from {@link - * BigQueryDefaultCoercions}. - */ - static BigQueryTypeCoercerBuilder builder() { - return BigQueryDefaultCoercions.builder(); - } - - private BigQueryCoercion findCoercion(Class sourceClass, Class targetClass) { - Map, BigQueryCoercion> bySourceMap = this.allCoercions.get(sourceClass); - // AutoValue generated concrete classes are registered with their abstract classes and not the - // concrete class. Lets make sure the we can find the registered abstract class for such - // classes. The abstract class in these cases would be the super class of the generated - // AutoValue concrete classes. - if (bySourceMap == null) { - Class registeredAbstractClass = sourceClass.getSuperclass(); - bySourceMap = this.allCoercions.get(registeredAbstractClass); - } - // If we still can't find the coercion source class entry then just return. - if (bySourceMap == null) { - return null; - } - return (BigQueryCoercion) bySourceMap.get(targetClass); - } -} diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercerBuilder.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercerBuilder.java deleted file mode 100644 index 8539515ed16a..000000000000 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercerBuilder.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2023 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.api.core.InternalApi; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; - -/** - * A builder to create {@link BigQueryTypeCoercer} to perform the coercion of custom user defined - * types. - */ -@InternalApi -class BigQueryTypeCoercerBuilder { - - private final Map, Map, BigQueryCoercion>> allCoercions; - - BigQueryTypeCoercerBuilder() { - this.allCoercions = new HashMap<>(); - } - - /** - * registers a {@link BigQueryCoercion} - * - * @param coercion A {@link BigQueryCoercion} to register with this builder. - */ - BigQueryTypeCoercerBuilder registerTypeCoercion(BigQueryCoercion coercion) { - Type[] typeArguments = - ((ParameterizedType) coercion.getClass().getGenericInterfaces()[0]) - .getActualTypeArguments(); - Class sourceClass = (Class) typeArguments[0]; - Class targetClass = (Class) typeArguments[1]; - this.registerInternal(coercion, sourceClass, targetClass); - return this; - } - - /** - * registers a {@link BigQueryCoercion} using an implementation of {@link Function} - * - * @param function A {@link Function} to register with the builder. - * @param sourceClass the source class - * @param targetClass the target class - */ - BigQueryTypeCoercerBuilder registerTypeCoercion( - Function function, Class sourceClass, Class targetClass) { - this.registerInternal((BigQueryCoercion) function::apply, sourceClass, targetClass); - return this; - } - - /** builds the {@link BigQueryTypeCoercer} with all the registered {@link BigQueryCoercion}s. */ - BigQueryTypeCoercer build() { - return new BigQueryTypeCoercer(this.allCoercions); - } - - private void registerInternal( - BigQueryCoercion coercion, Class sourceClass, Class targetClass) { - Map, BigQueryCoercion> mapBySource = - this.allCoercions.getOrDefault(sourceClass, new HashMap<>()); - mapBySource.put(targetClass, coercion); - this.allCoercions.putIfAbsent(sourceClass, mapBySource); - } -} 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 deleted file mode 100644 index aa21307db1ef..000000000000 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercionUtility.java +++ /dev/null @@ -1,572 +0,0 @@ -/* - * Copyright 2023 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.api.core.InternalApi; -import com.google.cloud.bigquery.FieldValue; -import com.google.cloud.bigquery.FieldValue.Attribute; -import com.google.cloud.bigquery.Range; -import java.math.BigDecimal; -import java.sql.Date; -import java.sql.Time; -import java.sql.Timestamp; -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.OffsetDateTime; -import java.time.Period; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.temporal.ChronoUnit; -import java.util.Calendar; -import java.util.concurrent.TimeUnit; -import org.apache.arrow.vector.PeriodDuration; -import org.apache.arrow.vector.util.Text; - -@InternalApi -class BigQueryTypeCoercionUtility { - private static final BigQueryJdbcCustomLogger LOG = - new BigQueryJdbcCustomLogger(BigQueryTypeCoercionUtility.class.getName()); - - /** Returns a defensively cloned Calendar instance or a new default Calendar if input is null. */ - static Calendar getSafeCalendar(Calendar cal) { - if (cal == null) { - return Calendar.getInstance(); - } - Object cloned = cal.clone(); - if (cloned instanceof Calendar) { - return (Calendar) cloned; - } - Calendar safeCal = Calendar.getInstance(); - if (cal.getTimeZone() != null) { - safeCal.setTimeZone(cal.getTimeZone()); - } - return safeCal; - } - - /** - * Converts a {@link Date} for reading/outbound operations (e.g., {@code getDate(..., Calendar)}) - * by shifting its wall-clock year, month, and day fields into the target {@link Calendar}'s - * timezone per the JDBC specification. - * - * @param date the date in system-default time representation - * @param cal the target Calendar containing the desired timezone - * @return the adjusted Date starting at 00:00:00 in the target Calendar's timezone - */ - static Date convertDateWithCalendar(Date date, Calendar cal) { - if (date == null || cal == null) { - return date; - } - ZoneId systemZone = ZoneId.systemDefault(); - ZoneId targetZone = cal.getTimeZone().toZoneId(); - if (systemZone.equals(targetZone)) { - return date; - } - LocalDate localDate = date.toLocalDate(); - ZonedDateTime zdt = localDate.atStartOfDay(targetZone); - return new Date(zdt.toInstant().toEpochMilli()); - } - - /** - * Converts a {@link Date} for writing/inbound parameter setting operations (e.g., {@code - * setDate(..., Calendar)}) by extracting its local date fields as interpreted in the target - * {@link Calendar}'s timezone and normalizing them back into start-of-day in the system-default - * timezone. - * - * @param date the date instant specified relative to the target Calendar - * @param cal the Calendar containing the source timezone - * @return the normalized Date starting at 00:00:00 in the system-default timezone - */ - static Date convertDateToCalendar(Date date, Calendar cal) { - if (date == null || cal == null) { - return date; - } - ZoneId systemZone = ZoneId.systemDefault(); - ZoneId targetZone = cal.getTimeZone().toZoneId(); - if (systemZone.equals(targetZone)) { - return date; - } - LocalDate localDate = Instant.ofEpochMilli(date.getTime()).atZone(targetZone).toLocalDate(); - ZonedDateTime zdt = localDate.atStartOfDay(systemZone); - return new Date(zdt.toInstant().toEpochMilli()); - } - - /** - * Converts a java.sql.Time by shifting its wall-clock hour, minute, second, and millisecond - * fields into the target Calendar's timezone per JDBC specification. - */ - static Time convertTimeWithCalendar(Time time, Calendar cal) { - if (time == null || cal == null) { - return time; - } - ZoneId systemZone = ZoneId.systemDefault(); - ZoneId targetZone = cal.getTimeZone().toZoneId(); - if (systemZone.equals(targetZone)) { - return time; - } - Calendar defaultCal = Calendar.getInstance(); - defaultCal.setTime(time); - - Calendar targetCal = getSafeCalendar(cal); - targetCal.set(Calendar.HOUR_OF_DAY, defaultCal.get(Calendar.HOUR_OF_DAY)); - targetCal.set(Calendar.MINUTE, defaultCal.get(Calendar.MINUTE)); - targetCal.set(Calendar.SECOND, defaultCal.get(Calendar.SECOND)); - targetCal.set(Calendar.MILLISECOND, defaultCal.get(Calendar.MILLISECOND)); - return new Time(targetCal.getTimeInMillis()); - } - - /** - * Converts a java.sql.Timestamp by shifting its wall-clock fields into the target Calendar's - * timezone per JDBC specification while preserving nanosecond precision. - */ - static Timestamp convertTimestampWithCalendar(Timestamp timestamp, Calendar cal) { - if (timestamp == null || cal == null) { - return timestamp; - } - ZoneId systemZone = ZoneId.systemDefault(); - ZoneId targetZone = cal.getTimeZone().toZoneId(); - if (systemZone.equals(targetZone)) { - return timestamp; - } - LocalDateTime ldt = timestamp.toLocalDateTime(); - ZonedDateTime zdt = ldt.atZone(targetZone); - Timestamp adjustedTimestamp = Timestamp.from(zdt.toInstant()); - adjustedTimestamp.setNanos(timestamp.getNanos()); - return adjustedTimestamp; - } - - static BigQueryTypeCoercer INSTANCE; - - static { - INSTANCE = - BigQueryTypeCoercer.builder() - .registerTypeCoercion(new FieldValueToString()) - .registerTypeCoercion(new FieldValueToInteger()) - .registerTypeCoercion(new FieldValueToFloat()) - .registerTypeCoercion(new FieldValueToShort()) - .registerTypeCoercion(new FieldValueToLong()) - .registerTypeCoercion(new FieldValueToDouble()) - .registerTypeCoercion(new FieldValueToBigDecimal()) - .registerTypeCoercion(new FieldValueToBoolean()) - .registerTypeCoercion(new FieldValueToBytesArray()) - .registerTypeCoercion(new FieldValueToTimestamp()) - .registerTypeCoercion(new FieldValueToTime()) - .registerTypeCoercion(new FieldValueToDate()) - .registerTypeCoercion(new FieldValueToObject()) - .registerTypeCoercion(new StringToBytesArray()) - .registerTypeCoercion(new RangeToString()) - .registerTypeCoercion(new IntegerToLong()) - .registerTypeCoercion(new BytesArrayToString()) - - // Read API Type coercions - .registerTypeCoercion( - (LocalDateTime ldt) -> Timestamp.from(ldt.toInstant(ZoneOffset.UTC)), - LocalDateTime.class, - Timestamp.class) - .registerTypeCoercion(Text::toString, Text.class, String.class) - .registerTypeCoercion(new TextToInteger()) - .registerTypeCoercion(new LongToTimestamp()) - .registerTypeCoercion(new LongToTime()) - .registerTypeCoercion(new IntegerToDate()) - .registerTypeCoercion( - (Timestamp ts) -> - Date.valueOf(ts.toInstant().atOffset(ZoneOffset.UTC).toLocalDate()), - Timestamp.class, - Date.class) - .registerTypeCoercion( - (Timestamp ts) -> - Time.valueOf(ts.toInstant().atOffset(ZoneOffset.UTC).toLocalTime()), - Timestamp.class, - Time.class) - .registerTypeCoercion( - (Time time) -> // Per JDBC spec, the date component should be 1970-01-01 - Timestamp.from( - LocalDateTime.of(LocalDate.ofEpochDay(0), time.toLocalTime()) - .toInstant(ZoneOffset.UTC)), - Time.class, - Timestamp.class) - .registerTypeCoercion( - (Date date) -> new Timestamp(date.getTime()), Date.class, Timestamp.class) - .registerTypeCoercion( - (LocalDateTime ldt) -> Date.valueOf(ldt.toLocalDate()), - LocalDateTime.class, - Date.class) - .registerTypeCoercion( - (LocalDateTime ldt) -> { - // Custom conversion is used to preserve sub-second (millisecond) precision, - // as standard java.sql.Time.valueOf(LocalTime) truncates milliseconds. - long millisOfDay = TimeUnit.NANOSECONDS.toMillis(ldt.toLocalTime().toNanoOfDay()); - long localMillis = TimeZoneCache.getLocalMillis(millisOfDay); - return new Time(localMillis); - }, - LocalDateTime.class, - Time.class) - .registerTypeCoercion((Date date) -> date.toLocalDate(), Date.class, LocalDate.class) - .registerTypeCoercion( - (Time time) -> { - // Custom conversion is used to preserve sub-second (millisecond) precision, - // as standard java.sql.Time.toLocalTime() truncates milliseconds. - long millis = time.getTime(); - long localMillis = millis + TimeZoneCache.getOffset(millis); - return LocalTime.ofNanoOfDay(TimeUnit.MILLISECONDS.toNanos(localMillis)); - }, - Time.class, - LocalTime.class) - .registerTypeCoercion( - (Timestamp ts) -> ts.toInstant().atOffset(ZoneOffset.UTC).toLocalDateTime(), - Timestamp.class, - LocalDateTime.class) - .registerTypeCoercion( - (Timestamp ts) -> ts.toInstant().atOffset(ZoneOffset.UTC), - Timestamp.class, - OffsetDateTime.class) - .registerTypeCoercion((Timestamp ts) -> ts.toInstant(), Timestamp.class, Instant.class) - .registerTypeCoercion(new TimestampToString()) - .registerTypeCoercion(new TimeToString()) - .registerTypeCoercion((Long l) -> l != 0L, Long.class, Boolean.class) - .registerTypeCoercion((Double d) -> d != 0.0d, Double.class, Boolean.class) - .registerTypeCoercion( - (BigDecimal bd) -> bd.compareTo(BigDecimal.ZERO) != 0, - BigDecimal.class, - Boolean.class) - .registerTypeCoercion((Integer i) -> i != 0, Integer.class, Boolean.class) - .registerTypeCoercion((Float f) -> f != 0.0f, Float.class, Boolean.class) - .registerTypeCoercion((Short s) -> s.shortValue() != 0, Short.class, Boolean.class) - .registerTypeCoercion((Boolean b) -> b ? 1L : 0L, Boolean.class, Long.class) - .registerTypeCoercion((Boolean b) -> b ? 1.0d : 0.0d, Boolean.class, Double.class) - .registerTypeCoercion((Boolean b) -> b ? 1.0f : 0.0f, Boolean.class, Float.class) - .registerTypeCoercion((Boolean b) -> (short) (b ? 1 : 0), Boolean.class, Short.class) - .registerTypeCoercion((Boolean b) -> (byte) (b ? 1 : 0), Boolean.class, Byte.class) - .registerTypeCoercion( - (Boolean b) -> b ? BigDecimal.ONE : BigDecimal.ZERO, - Boolean.class, - BigDecimal.class) - .registerTypeCoercion( - (Integer i) -> BigDecimal.valueOf(i), Integer.class, BigDecimal.class) - .registerTypeCoercion((Long l) -> BigDecimal.valueOf(l), Long.class, BigDecimal.class) - .registerTypeCoercion( - (Double d) -> BigDecimal.valueOf(d), Double.class, BigDecimal.class) - .registerTypeCoercion((Float f) -> BigDecimal.valueOf(f), Float.class, BigDecimal.class) - .registerTypeCoercion((String s) -> new BigDecimal(s), String.class, BigDecimal.class) - .registerTypeCoercion(new PeriodDurationToString()) - .registerTypeCoercion(unused -> (byte) 0, Void.class, Byte.class) - .registerTypeCoercion(unused -> 0, Void.class, Integer.class) - .registerTypeCoercion(unused -> 0L, Void.class, Long.class) - .registerTypeCoercion(unused -> 0D, Void.class, Double.class) - .registerTypeCoercion(unused -> 0f, Void.class, Float.class) - .registerTypeCoercion(unused -> (short) 0, Void.class, Short.class) - .build(); - } - - private static class TimestampToString implements BigQueryCoercion { - private static final DateTimeFormatter FORMATTER = - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS"); - - @Override - public String coerce(Timestamp value) { - return FORMATTER.format(value.toLocalDateTime()); - } - } - - private static class TimeToString implements BigQueryCoercion { - private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss.SSS"); - - @Override - public String coerce(Time value) { - return FORMATTER.format(value.toLocalTime()); - } - } - - private static class PeriodDurationToString implements BigQueryCoercion { - - @Override - public String coerce(PeriodDuration value) { - StringBuilder builder = new StringBuilder(); - - // Conversion of Period - Period period = value.getPeriod().normalized(); - - builder - .append(period.getYears()) - .append("-") - .append(period.getMonths()) - .append(" ") - .append(period.getDays()) - .append(" "); - - // Conversion of Duration - Duration duration = value.getDuration(); - if (duration.isNegative()) { - builder.append("-"); - duration = duration.negated(); - } - long hours = duration.toHours(); - duration = duration.minusHours(hours); - long minutes = duration.toMinutes(); - duration = duration.minusMinutes(minutes); - long seconds = duration.getSeconds(); - duration = duration.minusSeconds(seconds); - long microseconds = duration.toNanos() / 1000; - - builder - .append(hours) - .append(":") - .append(minutes) - .append(":") - .append(seconds) - .append(".") - .append(microseconds); - - String result = builder.toString(); - result = result.replaceFirst("--", "-"); - - return result; - } - } - - private static class IntegerToDate implements BigQueryCoercion { - - @Override - public Date coerce(Integer value) { - // For example int 18993 represents 2022-01-01 - // Using LocalDate here to avoid this date getting affected by local time zones. - LocalDate date = LocalDate.ofEpochDay(Long.valueOf(value)); - return Date.valueOf(date); - } - } - - private static class LongToTime implements BigQueryCoercion { - - @Override - public Time coerce(Long value) { - // Note: BQ Time has a precision of up to six fractional digits (microsecond precision) - // but java.sql.Time only supports up to millisecond precision. So data after milliseconds is - // truncated. - long millisOfDay = value / 1000; - long localMillis = TimeZoneCache.getLocalMillis(millisOfDay); - return new Time(localMillis); - } - } - - private static class LongToTimestamp implements BigQueryCoercion { - - @Override - public Timestamp coerce(Long value) { - // Long value is in microseconds. All further calculations should account for the unit. - Instant instant = Instant.EPOCH.plus(value, ChronoUnit.MICROS); - // Timezone-agnostic conversion preserving exact point in time as mandated by JDBC spec - return Timestamp.from(instant); - } - } - - private static class TextToInteger implements BigQueryCoercion { - - @Override - public Integer coerce(Text value) { - return Integer.parseInt(value.toString()); - } - } - - private static class FieldValueToObject implements BigQueryCoercion { - - @Override - public Object coerce(FieldValue fieldValue) { - return fieldValue.getValue(); - } - } - - private static class FieldValueToDate implements BigQueryCoercion { - - @Override - public Date coerce(FieldValue fieldValue) { - return Date.valueOf(fieldValue.getStringValue()); - } - } - - private static class FieldValueToTime implements BigQueryCoercion { - - @Override - public Time coerce(FieldValue fieldValue) { - // Time ranges from 00:00:00 to 23:59:59.999999 in BigQuery - String strTime = fieldValue.getStringValue(); - try { - LocalTime localTime = LocalTime.parse(strTime); - // Convert LocalTime to milliseconds of the day. This correctly preserves millisecond - // precision and truncates anything smaller - long millisOfDay = TimeUnit.NANOSECONDS.toMillis(localTime.toNanoOfDay()); - // Adjust by local timezone offset to ensure correct wall-clock representation with - // millisecond precision - long localMillis = TimeZoneCache.getLocalMillis(millisOfDay); - return new Time(localMillis); - } catch (java.time.format.DateTimeParseException e) { - IllegalArgumentException ex = - new IllegalArgumentException( - "Cannot parse the value " + strTime + " to java.sql.Time", e); - LOG.severe(ex.getMessage(), ex); - throw ex; - } - } - } - - private static class FieldValueToTimestamp implements BigQueryCoercion { - - @Override - public Timestamp coerce(FieldValue fieldValue) { - String rawValue = fieldValue.getStringValue(); - // BigQuery DATETIME strings are formatted like "YYYY-MM-DD'T'HH:MM:SS.fffffffff" - // BigQuery TIMESTAMP strings are numeric epoch seconds. - if (rawValue.contains("T")) { - // It's a DATETIME string. - // Timestamp.valueOf() expects "yyyy-mm-dd hh:mm:ss.fffffffff" format. - return Timestamp.valueOf(rawValue.replace('T', ' ')); - } else { - // It's a TIMESTAMP numeric string. - long microseconds = fieldValue.getTimestampValue(); - Instant instant = Instant.EPOCH.plus(microseconds, ChronoUnit.MICROS); - // Timezone-agnostic conversion preserving exact point in time as mandated by JDBC spec - return Timestamp.from(instant); - } - } - } - - private static class FieldValueToBytesArray implements BigQueryCoercion { - - @Override - public byte[] coerce(FieldValue fieldValue) { - return fieldValue.getBytesValue(); - } - } - - private static class StringToBytesArray implements BigQueryCoercion { - - @Override - public byte[] coerce(String value) { - return value.getBytes(); - } - } - - private static class BytesArrayToString implements BigQueryCoercion { - - @Override - public String coerce(byte[] value) { - return java.util.Base64.getEncoder().encodeToString(value); - } - } - - private static class FieldValueToBoolean implements BigQueryCoercion { - - @Override - public Boolean coerce(FieldValue fieldValue) { - return !fieldValue.isNull() && fieldValue.getBooleanValue(); - } - } - - private static class FieldValueToBigDecimal implements BigQueryCoercion { - - @Override - public BigDecimal coerce(FieldValue fieldValue) { - return fieldValue.getNumericValue(); - } - } - - private static class FieldValueToDouble implements BigQueryCoercion { - - @Override - public Double coerce(FieldValue fieldValue) { - return fieldValue.getDoubleValue(); - } - } - - private static class FieldValueToLong implements BigQueryCoercion { - - @Override - public Long coerce(FieldValue fieldValue) { - return fieldValue.getLongValue(); - } - } - - private static class FieldValueToInteger implements BigQueryCoercion { - - @Override - public Integer coerce(FieldValue fieldValue) { - return (int) fieldValue.getLongValue(); - } - } - - private static class FieldValueToFloat implements BigQueryCoercion { - - @Override - public Float coerce(FieldValue fieldValue) { - return (float) fieldValue.getDoubleValue(); - } - } - - private static class FieldValueToShort implements BigQueryCoercion { - - @Override - public Short coerce(FieldValue fieldValue) { - return (short) fieldValue.getLongValue(); - } - } - - private static class FieldValueToString implements BigQueryCoercion { - - @Override - public String coerce(FieldValue fieldValue) { - if (Attribute.REPEATED.equals(fieldValue.getAttribute())) { // Case for Arrays - return fieldValue.getValue().toString(); - } - if (Attribute.RANGE.equals(fieldValue.getAttribute())) { // Range values - Range rangeValue = fieldValue.getRangeValue(); - return INSTANCE.coerceTo(String.class, rangeValue); - } - if (Attribute.RECORD.equals(fieldValue.getAttribute())) { // Case for Structs - return fieldValue.getRecordValue().toString(); - } - return fieldValue.getStringValue(); - } - } - - private static class IntegerToLong implements BigQueryCoercion { - - @Override - public Long coerce(Integer intValue) { - if (intValue == null) { - return 0L; - } - return Long.valueOf(intValue); - } - } - - private static class RangeToString implements BigQueryCoercion { - - @Override - public String coerce(Range value) { - FieldValue startValue = value.getStart(); - FieldValue endValue = value.getEnd(); - - String start = startValue.isNull() ? "UNBOUNDED" : startValue.getStringValue(); - String end = endValue.isNull() ? "UNBOUNDED" : endValue.getStringValue(); - // The start of a range is inclusive, and the end is exclusive. - return String.format("[%s, %s)", start, end); - } - } -} diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/ArrowFormatTypeBigQueryCoercionUtilityTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/ArrowFormatTypeBigQueryCoercionUtilityTest.java deleted file mode 100644 index 02f5e77c738a..000000000000 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/ArrowFormatTypeBigQueryCoercionUtilityTest.java +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.cloud.bigquery.jdbc; - -import static com.google.cloud.bigquery.jdbc.BigQueryTypeCoercionUtility.INSTANCE; -import static com.google.common.truth.Truth.assertThat; -import static java.time.Month.FEBRUARY; -import static java.time.Month.JANUARY; - -import com.google.cloud.bigquery.FieldElementType; -import com.google.cloud.bigquery.Range; -import com.google.cloud.bigquery.jdbc.rules.TimeZoneRule; -import java.math.BigDecimal; -import java.sql.Date; -import java.sql.Time; -import java.sql.Timestamp; -import java.time.Duration; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.Period; -import java.util.TimeZone; -import org.apache.arrow.vector.PeriodDuration; -import org.apache.arrow.vector.util.JsonStringArrayList; -import org.apache.arrow.vector.util.JsonStringHashMap; -import org.apache.arrow.vector.util.Text; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -public class ArrowFormatTypeBigQueryCoercionUtilityTest { - - @RegisterExtension public final TimeZoneRule timeZoneRule = new TimeZoneRule("UTC"); - - private static final Range RANGE_DATE = - Range.newBuilder() - .setType(FieldElementType.newBuilder().setType("DATE").build()) - .setStart("1970-01-02") - .setEnd("1970-03-04") - .build(); - - private static final Range RANGE_DATETIME = - Range.newBuilder() - .setType(FieldElementType.newBuilder().setType("DATETIME").build()) - .setStart("2014-08-19 05:41:35.220000") - .setEnd("2015-09-20 06:41:35.220000") - .build(); - - private static final Range RANGE_TIMESTAMP = - Range.newBuilder() - .setType(FieldElementType.newBuilder().setType("TIMESTAMP").build()) - .setStart("2014-08-19 12:41:35.220000+00:00") - .setEnd("2015-09-20 13:41:35.220000+01:00") - .build(); - - @Test - public void nullToString() { - assertThat(INSTANCE.coerceTo(String.class, null)).isNull(); - } - - @Test - public void JsonStringArrayListToString() { - JsonStringArrayList employeeList = new JsonStringArrayList<>(); - employeeList.add(1); - employeeList.add(2); - employeeList.add(3); - - assertThat(INSTANCE.coerceTo(String.class, employeeList)).isEqualTo("[1,2,3]"); - } - - @Test - public void localDateTimeToTimestamp() { - LocalDateTime localDatetime = LocalDateTime.of(1995, FEBRUARY, 23, 20, 15); - - assertThat(INSTANCE.coerceTo(Timestamp.class, localDatetime)) - .isEqualTo(Timestamp.valueOf(localDatetime)); - } - - @Test - public void textToString() { - Text text = new Text("Hello World!"); - - assertThat(INSTANCE.coerceTo(String.class, text)).isEqualTo("Hello World!"); - } - - @Test - public void nullToInteger() { - assertThat(INSTANCE.coerceTo(Integer.class, null)).isEqualTo(0); - } - - @Test - public void textToInteger() { - Text text = new Text("51423"); - - assertThat(INSTANCE.coerceTo(Integer.class, text)).isEqualTo(51423); - } - - @Test - public void longToInteger() { - assertThat(INSTANCE.coerceTo(Integer.class, 56L)).isEqualTo(56); - } - - @Test - public void bigDecimalToInteger() { - assertThat(INSTANCE.coerceTo(Integer.class, new BigDecimal("56"))).isEqualTo(56); - } - - @Test - public void nullToLong() { - assertThat(INSTANCE.coerceTo(Long.class, null)).isEqualTo(0L); - } - - @Test - public void bigDecimalToLong() { - assertThat(INSTANCE.coerceTo(Long.class, new BigDecimal("56"))).isEqualTo(56L); - } - - @Test - public void nullToDouble() { - assertThat(INSTANCE.coerceTo(Double.class, null)).isEqualTo(0D); - } - - @Test - public void bigDecimalToDouble() { - assertThat(INSTANCE.coerceTo(Double.class, new BigDecimal("56"))).isEqualTo(56D); - } - - @Test - public void nullToBoolean() { - assertThat(INSTANCE.coerceTo(Boolean.class, null)).isFalse(); - } - - @Test - public void nullToByteArray() { - assertThat(INSTANCE.coerceTo(byte[].class, null)).isNull(); - } - - @Test - public void nullToTimestamp() { - assertThat(INSTANCE.coerceTo(Timestamp.class, null)).isNull(); - } - - @Test - public void longToTimestamp() { - assertThat(INSTANCE.coerceTo(Timestamp.class, 1408452095220000L)) - .isEqualTo(new Timestamp(1408452095220L)); - } - - @Test - public void nullToTime() { - assertThat(INSTANCE.coerceTo(Time.class, null)).isNull(); - } - - @Test - public void longToTime() { - long value = 1408452095220000L; - // 1408452095220000 microseconds is 1408452095220 milliseconds. - // Since the test runs under UTC timezone by TimeZoneRule, expected localMillis is - // 1408452095220L. - assertThat(INSTANCE.coerceTo(Time.class, value)).isEqualTo(new Time(1408452095220L)); - } - - @Test - public void longToTimeInNonUTCTimeZone() { - TimeZone originalTimeZone = TimeZone.getDefault(); - try { - TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles")); - TimeZoneCache.reset(); - long value = 1408452095220000L; - // 1408452095220000 microseconds is 1408452095220 milliseconds. - // Under America/Los_Angeles (PDT, -7 hours offset in Aug 2014), the subtracted offset - // results in 1408452095220 - (-25200000) = 1408477295220L. - assertThat(INSTANCE.coerceTo(Time.class, value)).isEqualTo(new Time(1408477295220L)); - } finally { - TimeZone.setDefault(originalTimeZone); - TimeZoneCache.reset(); - } - } - - @Test - public void nullToDate() { - assertThat(INSTANCE.coerceTo(Date.class, null)).isNull(); - } - - @Test - public void integerToDate() { - LocalDate expectedDate = LocalDate.of(2022, JANUARY, 1); - assertThat(INSTANCE.coerceTo(Date.class, 18993).toLocalDate()).isEqualTo(expectedDate); - } - - @Test - public void periodDurationToString() { - Period period = Period.of(1, 3, 24); - Duration duration = Duration.ofHours(3).plusMinutes(45).plusSeconds(23).plusNanos(123456000); - PeriodDuration periodDuration = new PeriodDuration(period, duration); - assertThat(INSTANCE.coerceTo(String.class, periodDuration)).isEqualTo("1-3 24 3:45:23.123456"); - - Period period2 = Period.of(1, 6, -8); - Duration duration2 = Duration.ofHours(9).plusMinutes(43).plusSeconds(23).plusNanos(123456000); - PeriodDuration periodDuration2 = new PeriodDuration(period2, duration2); - assertThat(INSTANCE.coerceTo(String.class, periodDuration2)).isEqualTo("1-6 -8 9:43:23.123456"); - } - - // Range tests - - @Test - public void JsonStringHashMapToString() { - JsonStringHashMap employeeMap = new JsonStringHashMap<>(); - employeeMap.putIfAbsent("name1", "type1"); - employeeMap.putIfAbsent("name2", "type2"); - employeeMap.putIfAbsent("name3", "type3"); - - assertThat(INSTANCE.coerceTo(String.class, employeeMap)) - .isEqualTo("{\"name1\":\"type1\",\"name2\":\"type2\",\"name3\":\"type3\"}"); - } - - @Test - public void rangeDateToString() { - String expectedRangeDate = - String.format( - "[%s, %s)", - RANGE_DATE.getStart().getStringValue(), RANGE_DATE.getEnd().getStringValue()); - assertThat(INSTANCE.coerceTo(String.class, RANGE_DATE)).isEqualTo(expectedRangeDate); - } - - @Test - public void rangeDatetimeToString() { - String expectedRangeDate = - String.format( - "[%s, %s)", - RANGE_DATETIME.getStart().getStringValue(), RANGE_DATETIME.getEnd().getStringValue()); - assertThat(INSTANCE.coerceTo(String.class, RANGE_DATETIME)).isEqualTo(expectedRangeDate); - } - - @Test - public void rangeTimestampToString() { - String expectedRangeTimestamp = - String.format( - "[%s, %s)", - RANGE_TIMESTAMP.getStart().getStringValue(), RANGE_TIMESTAMP.getEnd().getStringValue()); - assertThat(INSTANCE.coerceTo(String.class, RANGE_TIMESTAMP)).isEqualTo(expectedRangeTimestamp); - } -} diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryDefaultCoercionsTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryDefaultCoercionsTest.java deleted file mode 100644 index fd969a548d2e..000000000000 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryDefaultCoercionsTest.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.cloud.bigquery.jdbc; - -import static com.google.cloud.bigquery.jdbc.BigQueryTypeCoercer.INSTANCE; -import static com.google.common.truth.Truth.assertThat; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.Arrays; -import java.util.Collection; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; - -public class BigQueryDefaultCoercionsTest { - - // migrated - public static Collection data() { - return Arrays.asList( - new Object[][] { - {"default BigQueryTypeCoercer", INSTANCE}, - {"customizable BigQueryTypeCoercer", BigQueryTypeCoercer.builder().build()} - }); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void stringToBoolean(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(Boolean.class, "true")).isTrue(); - assertThat(bigQueryTypeCoercer.coerceTo(Boolean.class, "false")).isFalse(); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void stringToInteger(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(Integer.class, "3452148")).isEqualTo(3452148); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void stringToBigInteger(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(BigInteger.class, "2147483647456")) - .isEqualTo(new BigInteger("2147483647456")); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void stringToLong(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(Long.class, "2147483647456")) - .isEqualTo(Long.valueOf("2147483647456")); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void stringToDouble(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(Double.class, "2147483647456.56684593495")) - .isEqualTo(Double.valueOf("2147483647456.56684593495")); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void stringToBigDecimal(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(BigDecimal.class, "2147483647456.56684593495")) - .isEqualTo(new BigDecimal("2147483647456.56684593495")); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void booleanToString(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(String.class, true)).isEqualTo("true"); - assertThat(bigQueryTypeCoercer.coerceTo(String.class, false)).isEqualTo("false"); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void booleanToInteger(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(Integer.class, true)).isEqualTo(1); - assertThat(bigQueryTypeCoercer.coerceTo(Integer.class, false)).isEqualTo(0); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void longToInteger(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(Integer.class, 2147483647L)).isEqualTo(2147483647); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void longToShort(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(Short.class, 32000L)).isEqualTo((short) 32000); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void longToByte(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(Byte.class, 127L)).isEqualTo((byte) 127); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void longToDouble(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(Double.class, 2147483647456L)) - .isEqualTo(Double.valueOf("2147483647456")); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void longToString(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(String.class, 2147483647456L)) - .isEqualTo("2147483647456"); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void doubleToFloat(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(Float.class, Double.valueOf("4567.213245"))) - .isEqualTo(Float.valueOf("4567.213245")); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void doubleToLong(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(Long.class, Double.valueOf("2147483647456.213245"))) - .isEqualTo(2147483647456L); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void doubleToInteger(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(Integer.class, Double.valueOf("21474836.213245"))) - .isEqualTo(21474836); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void doubleToBigDecimal(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(BigDecimal.class, Double.valueOf("21474836.213245"))) - .isEqualTo(new BigDecimal("21474836.213245")); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void doubleToString(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(String.class, Double.valueOf("21474836.213245"))) - .isEqualTo("2.1474836213245E7"); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void floatToInteger(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(Integer.class, 62356.45f)).isEqualTo(62356); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void floatToDouble(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(Double.class, 62356.45f)) - .isEqualTo(Double.valueOf(62356.45f)); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void floatToString(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(String.class, 62356.45f)).isEqualTo("62356.45"); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void bigIntegerToLong(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(Long.class, new BigInteger("2147483647"))) - .isEqualTo(2147483647L); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void bigIntegerToBigDecimal(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(BigDecimal.class, new BigInteger("2147483647"))) - .isEqualTo(new BigDecimal("2147483647")); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void bigIntegerToString(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(String.class, new BigInteger("2147483647"))) - .isEqualTo("2147483647"); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void bigDecimalToDouble(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(Double.class, new BigDecimal("2147483647.74356"))) - .isEqualTo(2147483647.74356); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void bigDecimalToBigInteger(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(BigInteger.class, new BigDecimal("2147483647.74356"))) - .isEqualTo(new BigInteger("2147483647")); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void bigDecimalToInteger(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(Integer.class, new BigDecimal("2147483647.74356"))) - .isEqualTo(2147483647); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void bigDecimalToLong(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(Long.class, new BigDecimal("2147483647.74356"))) - .isEqualTo(2147483647L); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void bigDecimalToString(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(String.class, new BigDecimal("2147483647.74356"))) - .isEqualTo("2147483647.74356"); - } - - @ParameterizedTest(name = "{index}: {0}") - @MethodSource("data") - public void nullToBoolean(String label, BigQueryTypeCoercer bigQueryTypeCoercer) { - assertThat(bigQueryTypeCoercer.coerceTo(Boolean.class, null)).isFalse(); - } -} diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercerBuilderTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercerBuilderTest.java deleted file mode 100644 index 4131cb24b236..000000000000 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercerBuilderTest.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.cloud.bigquery.jdbc; - -import static com.google.common.truth.Truth.assertThat; - -import com.google.cloud.bigquery.jdbc.TestType.Text; -import org.junit.jupiter.api.Test; - -public class BigQueryTypeCoercerBuilderTest { - - @Test - public void shouldBeAbleToConvertCustomTypes() { - byte[] bytesArray = {72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33}; - Text text = new Text(bytesArray); - - BigQueryTypeCoercer bigQueryTypeCoercer = - new BigQueryTypeCoercerBuilder().registerTypeCoercion(new TextToStringCoercion()).build(); - - assertThat(bigQueryTypeCoercer.coerceTo(String.class, text)).isEqualTo("Hello World!"); - } - - private static class TextToStringCoercion implements BigQueryCoercion { - @Override - public String coerce(Text value) { - return new String(value.getBytes()); - } - } -} diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercerTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercerTest.java deleted file mode 100644 index f05a9b80632a..000000000000 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryTypeCoercerTest.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.cloud.bigquery.jdbc; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import com.google.cloud.bigquery.exception.BigQueryJdbcCoercionException; -import com.google.cloud.bigquery.exception.BigQueryJdbcCoercionNotFoundException; -import com.google.cloud.bigquery.jdbc.TestType.Text; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.function.Function; -import org.junit.jupiter.api.Test; - -public class BigQueryTypeCoercerTest { - - @Test - public void shouldReturnSameValueWhenTargetTypeIsSameAsSourceType() { - assertThat(BigQueryTypeCoercer.INSTANCE.coerceTo(Integer.class, 56)).isEqualTo(56); - assertThat(BigQueryTypeCoercer.INSTANCE.coerceTo(Long.class, 56L)).isEqualTo(56L); - } - - @Test - public void shouldBeAbleToComposeMultipleCoercions() { - StringToBigDecimal stringToBigDecimal = new StringToBigDecimal(); - BigDecimalToBigInteger bigDecimalToBigInteger = new BigDecimalToBigInteger(); - - Function composedCoercion = - stringToBigDecimal.andThen(bigDecimalToBigInteger); - - BigQueryTypeCoercer bigQueryTypeCoercer = - new BigQueryTypeCoercerBuilder() - .registerTypeCoercion(composedCoercion, String.class, BigInteger.class) - .build(); - - assertThat(bigQueryTypeCoercer.coerceTo(BigInteger.class, "123567.66884")) - .isEqualTo(BigInteger.valueOf(123567)); - } - - @Test - public void shouldThrowCoercionNotFoundException() { - byte[] bytesArray = {72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33}; - Text text = new Text(bytesArray); - - BigQueryJdbcCoercionNotFoundException exception = - assertThrows( - BigQueryJdbcCoercionNotFoundException.class, - () -> BigQueryTypeCoercer.INSTANCE.coerceTo(Long.class, text)); - assertThat(exception.getMessage()) - .isEqualTo( - "Coercion not found for " - + "[com.google.cloud.bigquery.jdbc.TestType.Text -> java.lang.Long]" - + " conversion"); - } - - @Test - public void shouldThrowCoercionException() { - BigQueryTypeCoercer bigQueryTypeCoercer = - new BigQueryTypeCoercerBuilder() - .registerTypeCoercion(Math::toIntExact, Long.class, Integer.class) - .build(); - BigQueryJdbcCoercionException exception = - assertThrows( - BigQueryJdbcCoercionException.class, - () -> bigQueryTypeCoercer.coerceTo(Integer.class, 2147483648L)); - assertThat(exception.getMessage()).isEqualTo("Coercion error\ninteger overflow"); - assertThat(exception.getCause()).isInstanceOf(ArithmeticException.class); - } - - private static class StringToBigDecimal implements BigQueryCoercion { - - @Override - public BigDecimal coerce(String value) { - return new BigDecimal(value); - } - } - - private static class BigDecimalToBigInteger implements BigQueryCoercion { - - @Override - public BigInteger coerce(BigDecimal value) { - return value.toBigInteger(); - } - } -} 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 deleted file mode 100644 index 7b24e389f853..000000000000 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/FieldValueTypeBigQueryCoercionUtilityTest.java +++ /dev/null @@ -1,429 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.cloud.bigquery.jdbc; - -import static com.google.cloud.bigquery.FieldValue.Attribute.PRIMITIVE; -import static com.google.cloud.bigquery.FieldValue.Attribute.RANGE; -import static com.google.cloud.bigquery.FieldValue.Attribute.RECORD; -import static com.google.cloud.bigquery.FieldValue.Attribute.REPEATED; -import static com.google.cloud.bigquery.jdbc.BigQueryTypeCoercionUtility.INSTANCE; -import static com.google.common.truth.Truth.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import com.google.cloud.bigquery.FieldElementType; -import com.google.cloud.bigquery.FieldValue; -import com.google.cloud.bigquery.FieldValueList; -import com.google.cloud.bigquery.Range; -import com.google.cloud.bigquery.exception.BigQueryJdbcCoercionException; -import com.google.cloud.bigquery.jdbc.rules.TimeZoneRule; -import com.google.common.collect.ImmutableList; -import java.math.BigDecimal; -import java.sql.Date; -import java.sql.Time; -import java.sql.Timestamp; -import java.time.Instant; -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; - -public class FieldValueTypeBigQueryCoercionUtilityTest { - @RegisterExtension public final TimeZoneRule timeZoneRule = new TimeZoneRule("UTC"); - - private static final FieldValue STRING_VALUE = FieldValue.of(PRIMITIVE, "sample-string"); - private static final FieldValue INTEGER_VALUE = FieldValue.of(PRIMITIVE, "345"); - private static final FieldValue FLOAT_VALUE = FieldValue.of(PRIMITIVE, "345.21"); - private static final FieldValue SHORT_VALUE = FieldValue.of(PRIMITIVE, "345"); - private static final FieldValue LONG_VALUE = FieldValue.of(PRIMITIVE, "4374218905"); - private static final FieldValue DOUBLE_VALUE = FieldValue.of(PRIMITIVE, "56453.458393"); - private static final FieldValue BIG_DECIMAL_VALUE = FieldValue.of(PRIMITIVE, "56453.458393"); - private static final FieldValue BASE64_ENCODED_VALUE = - FieldValue.of(PRIMITIVE, "SGVsbG8gV29ybGQK"); // Hello World! - private static final FieldValue TIMESTAMP_VALUE = FieldValue.of(PRIMITIVE, "1408452095.22"); - private static final FieldValue DATE_VALUE = FieldValue.of(PRIMITIVE, "2023-03-13"); - private static final FieldValue TIME_VALUE = FieldValue.of(PRIMITIVE, "23:59:59"); - private static final FieldValue TIME_WITH_NANOSECOND_VALUE = - FieldValue.of(PRIMITIVE, "23:59:59.99999"); - private static final FieldValue TRUE_VALUE = FieldValue.of(PRIMITIVE, "true"); - private static final FieldValue FALSE_VALUE = FieldValue.of(PRIMITIVE, "false"); - private static final FieldValue NULL_VALUE = FieldValue.of(PRIMITIVE, null); - private static final FieldValue INTEGER_ARRAY = - FieldValue.of( - REPEATED, - FieldValueList.of( - ImmutableList.of(FieldValue.of(PRIMITIVE, 1), FieldValue.of(PRIMITIVE, 2)))); - private static final FieldValue RECORD_VALUE = - FieldValue.of( - RECORD, ImmutableList.of(INTEGER_VALUE, STRING_VALUE, TIME_VALUE, INTEGER_ARRAY)); - - private static final Range RANGE_DATE = - Range.newBuilder() - .setType(FieldElementType.newBuilder().setType("DATE").build()) - .setStart("1970-01-02") - .setEnd("1970-03-04") - .build(); - - private static final Range RANGE_DATETIME = - Range.newBuilder() - .setType(FieldElementType.newBuilder().setType("DATETIME").build()) - .setStart("2014-08-19 05:41:35.220000") - .setEnd("2015-09-20 06:41:35.220000") - .build(); - - private static final Range RANGE_TIMESTAMP = - Range.newBuilder() - .setType(FieldElementType.newBuilder().setType("TIMESTAMP").build()) - .setStart("2014-08-19 12:41:35.220000+00:00") - .setEnd("2015-09-20 13:41:35.220000+01:00") - .build(); - - private static final FieldValue RANGE_DATE_VALUE = FieldValue.of(RANGE, RANGE_DATE); - private static final FieldValue RANGE_DATE_TIME_VALUE = FieldValue.of(RANGE, RANGE_DATETIME); - private static final FieldValue RANGE_TIMESTAMP_VALUE = FieldValue.of(RANGE, RANGE_TIMESTAMP); - - @Test - public void fieldValueToStringRangeDate() { - String expectedRangeDate = - String.format( - "[%s, %s)", - RANGE_DATE.getStart().getStringValue(), RANGE_DATE.getEnd().getStringValue()); - assertThat(INSTANCE.coerceTo(String.class, RANGE_DATE_VALUE)).isEqualTo(expectedRangeDate); - } - - @Test - public void rangeDateToString() { - String expectedRangeDate = - String.format( - "[%s, %s)", - RANGE_DATE.getStart().getStringValue(), RANGE_DATE.getEnd().getStringValue()); - assertThat(INSTANCE.coerceTo(String.class, RANGE_DATE)).isEqualTo(expectedRangeDate); - } - - @Test - public void fieldValueToStringRangeDatetime() { - String expectedRangeDatetime = - String.format( - "[%s, %s)", - RANGE_DATETIME.getStart().getStringValue(), RANGE_DATETIME.getEnd().getStringValue()); - assertThat(INSTANCE.coerceTo(String.class, RANGE_DATE_TIME_VALUE)) - .isEqualTo(expectedRangeDatetime); - } - - @Test - public void rangeDatetimeToString() { - String expectedRangeDate = - String.format( - "[%s, %s)", - RANGE_DATETIME.getStart().getStringValue(), RANGE_DATETIME.getEnd().getStringValue()); - assertThat(INSTANCE.coerceTo(String.class, RANGE_DATETIME)).isEqualTo(expectedRangeDate); - } - - @Test - public void fieldValueToStringRangeTimestamp() { - String expectedRangeTimestamp = - String.format( - "[%s, %s)", - RANGE_TIMESTAMP.getStart().getStringValue(), RANGE_TIMESTAMP.getEnd().getStringValue()); - assertThat(INSTANCE.coerceTo(String.class, RANGE_TIMESTAMP_VALUE)) - .isEqualTo(expectedRangeTimestamp); - } - - @Test - public void rangeTimestampToString() { - String expectedRangeTimestamp = - String.format( - "[%s, %s)", - RANGE_TIMESTAMP.getStart().getStringValue(), RANGE_TIMESTAMP.getEnd().getStringValue()); - assertThat(INSTANCE.coerceTo(String.class, RANGE_TIMESTAMP)).isEqualTo(expectedRangeTimestamp); - } - - @Test - public void fieldValueToString() { - assertThat(INSTANCE.coerceTo(String.class, STRING_VALUE)).isEqualTo("sample-string"); - } - - @Test - public void fieldValueToStringWhenNull() { - assertThat(INSTANCE.coerceTo(String.class, null)).isNull(); - } - - @Test - public void fieldValueToStringWhenInnerValueIsNull() { - assertThat(INSTANCE.coerceTo(String.class, NULL_VALUE)).isNull(); - } - - @Test - public void fieldValueToStringWhenInnerValueIsAnArray() { - assertThat(INSTANCE.coerceTo(String.class, INTEGER_ARRAY)) - .isEqualTo( - "[FieldValue{attribute=PRIMITIVE, value=1, useInt64Timestamps=false}, FieldValue{attribute=PRIMITIVE, value=2, useInt64Timestamps=false}]"); - } - - @Test - public void fieldValueToInteger() { - assertThat(INSTANCE.coerceTo(Integer.class, INTEGER_VALUE)).isEqualTo(345); - } - - @Test - public void fieldValueToIntegerWhenNull() { - assertThat(INSTANCE.coerceTo(Integer.class, null)).isEqualTo(0); - } - - @Test - public void fieldValueToIntegerWhenInnerValueIsNull() { - assertThat(INSTANCE.coerceTo(Integer.class, NULL_VALUE)).isEqualTo(0); - } - - @Test - public void fieldValueToFloat() { - assertThat(INSTANCE.coerceTo(Float.class, FLOAT_VALUE)).isEqualTo(345.21f); - } - - @Test - public void fieldValueToFloatWhenNull() { - assertThat(INSTANCE.coerceTo(Float.class, null)).isEqualTo(0f); - } - - @Test - public void fieldValueToFloatWhenInnerValueNull() { - assertThat(INSTANCE.coerceTo(Float.class, NULL_VALUE)).isEqualTo(0f); - } - - @Test - public void fieldValueToShort() { - assertThat(INSTANCE.coerceTo(Short.class, SHORT_VALUE)).isEqualTo((short) 345); - } - - @Test - public void fieldValueToShortWhenNull() { - assertThat(INSTANCE.coerceTo(Short.class, null)).isEqualTo((short) 0); - } - - @Test - public void fieldValueToShortWhenInnerValueNull() { - assertThat(INSTANCE.coerceTo(Short.class, NULL_VALUE)).isEqualTo((short) 0); - } - - @Test - public void fieldValueToLong() { - assertThat(INSTANCE.coerceTo(Long.class, LONG_VALUE)).isEqualTo(4374218905L); - } - - @Test - public void fieldValueToLongWhenNull() { - assertThat(INSTANCE.coerceTo(Long.class, null)).isEqualTo(0L); - } - - @Test - public void fieldValueToLongWhenInnerValueIsNull() { - assertThat(INSTANCE.coerceTo(Long.class, NULL_VALUE)).isEqualTo(0L); - } - - @Test - public void fieldValueToDouble() { - assertThat(INSTANCE.coerceTo(Double.class, DOUBLE_VALUE)).isEqualTo(56453.458393D); - } - - @Test - public void fieldValueToDoubleWhenNull() { - assertThat(INSTANCE.coerceTo(Double.class, null)).isEqualTo(0D); - } - - @Test - public void fieldValueToDoubleWhenInnerValueIsNull() { - assertThat(INSTANCE.coerceTo(Double.class, NULL_VALUE)).isEqualTo(0D); - } - - @Test - public void fieldValueToBigDecimal() { - assertThat(INSTANCE.coerceTo(BigDecimal.class, BIG_DECIMAL_VALUE)) - .isEqualTo(new BigDecimal("56453.458393")); - } - - @Test - public void fieldValueToBigDecimalWhenNull() { - assertThat(INSTANCE.coerceTo(BigDecimal.class, null)).isNull(); - } - - @Test - public void fieldValueToBigDecimalWhenInnerValueIsNull() { - assertThat(INSTANCE.coerceTo(BigDecimal.class, NULL_VALUE)).isNull(); - } - - @Test - public void fieldValueToBoolean() { - assertThat(INSTANCE.coerceTo(Boolean.class, TRUE_VALUE)).isTrue(); - assertThat(INSTANCE.coerceTo(Boolean.class, FALSE_VALUE)).isFalse(); - } - - @Test - public void fieldValueToBooleanWhenNull() { - assertThat(INSTANCE.coerceTo(Boolean.class, null)).isFalse(); - } - - @Test - public void fieldValueToBooleanWhenInnerValueIsNull() { - assertThat(INSTANCE.coerceTo(Boolean.class, NULL_VALUE)).isFalse(); - } - - @Test - public void fieldValueToBytesArray() { - assertThat(INSTANCE.coerceTo(byte[].class, BASE64_ENCODED_VALUE)) - .isEqualTo(new byte[] {72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 10}); - } - - @Test - public void fieldValueToBytesArrayWhenNull() { - assertThat(INSTANCE.coerceTo(byte[].class, null)).isNull(); - } - - @Test - public void fieldValueToBytesArrayWhenInnerValueIsNull() { - assertThat(INSTANCE.coerceTo(byte[].class, NULL_VALUE)).isNull(); - } - - @Test - public void fieldValueToTimestamp() { - Instant instant = Instant.EPOCH.plus(TIMESTAMP_VALUE.getTimestampValue(), ChronoUnit.MICROS); - assertThat(INSTANCE.coerceTo(Timestamp.class, TIMESTAMP_VALUE)) - .isEqualTo(Timestamp.from(instant)); - } - - @Test - public void fieldValueToTimestampWhenNull() { - assertThat(INSTANCE.coerceTo(Timestamp.class, null)).isNull(); - } - - @Test - public void fieldValueToTimestampWhenInnerValueIsNull() { - assertThat(INSTANCE.coerceTo(Timestamp.class, NULL_VALUE)).isNull(); - } - - @Test - public void fieldValueToTime() { - LocalTime expectedTime = LocalTime.of(23, 59, 59); - assertThat(INSTANCE.coerceTo(Time.class, TIME_VALUE)).isEqualTo(Time.valueOf(expectedTime)); - // expectedTimeWithNanos has 999 milliseconds, giving 86399999 ms of day. - // Since the test runs under UTC timezone by TimeZoneRule, expected localMillis is 86399999L. - assertThat(INSTANCE.coerceTo(Time.class, TIME_WITH_NANOSECOND_VALUE)) - .isEqualTo(new Time(86399999L)); - } - - @Test - public void fieldValueToTimeInNonUTCTimeZone() { - TimeZone originalTimeZone = TimeZone.getDefault(); - try { - java.util.TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles")); - TimeZoneCache.reset(); - // 23:59:59.99999 yields 86399999 milliseconds. - // Under America/Los_Angeles on 1970-01-01 (PST, -8 hours offset), - // the subtracted offset results in 86399999 - (-28800000) = 115199999L. - assertThat(INSTANCE.coerceTo(Time.class, TIME_WITH_NANOSECOND_VALUE)) - .isEqualTo(new Time(115199999L)); - } finally { - TimeZone.setDefault(originalTimeZone); - TimeZoneCache.reset(); - } - } - - @Test - public void fieldValueToTimeWhenNull() { - assertThat(INSTANCE.coerceTo(Time.class, null)).isNull(); - } - - @Test - public void fieldValueToTimeWhenInnerValueIsNull() { - assertThat(INSTANCE.coerceTo(Time.class, NULL_VALUE)).isNull(); - } - - @Test - public void fieldValueToTimeWithInvalidValue() { - FieldValue invalidTime = FieldValue.of(PRIMITIVE, "99:99:99"); - - BigQueryJdbcCoercionException coercionException = - assertThrows( - BigQueryJdbcCoercionException.class, () -> INSTANCE.coerceTo(Time.class, invalidTime)); - assertThat(coercionException.getCause()).isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void fieldValueToDate() { - LocalDate expectedDate = LocalDate.of(2023, 3, 13); - assertThat(INSTANCE.coerceTo(Date.class, DATE_VALUE)).isEqualTo(Date.valueOf(expectedDate)); - } - - @Test - public void fieldValueToDateWhenNull() { - assertThat(INSTANCE.coerceTo(Date.class, null)).isNull(); - } - - @Test - public void fieldValueToDateWhenInnerValueIsNull() { - assertThat(INSTANCE.coerceTo(Date.class, NULL_VALUE)).isNull(); - } - - @Test - public void fieldValueToObject() { - assertThat(INSTANCE.coerceTo(Object.class, RECORD_VALUE)) - .isEqualTo(ImmutableList.of(INTEGER_VALUE, STRING_VALUE, TIME_VALUE, INTEGER_ARRAY)); - } - - @Test - public void fieldValueToObjectWhenNull() { - assertThat(INSTANCE.coerceTo(Object.class, null)).isNull(); - } - - @Test - public void fieldValueToObjectWhenInnerValueIsNull() { - assertThat(INSTANCE.coerceTo(Object.class, NULL_VALUE)).isNull(); - } - - @Test - public void testCalendarConversions() { - assertThat(BigQueryTypeCoercionUtility.convertDateWithCalendar(null, null)).isNull(); - assertThat(BigQueryTypeCoercionUtility.convertTimeWithCalendar(null, null)).isNull(); - assertThat(BigQueryTypeCoercionUtility.convertTimestampWithCalendar(null, null)).isNull(); - - Date rawDate = Date.valueOf("2026-07-17"); - Time rawTime = Time.valueOf("14:30:00"); - Timestamp rawTimestamp = Timestamp.valueOf("2026-07-17 14:30:00.123456789"); - - // Null calendar returns input unchanged - assertThat(BigQueryTypeCoercionUtility.convertDateWithCalendar(rawDate, null)) - .isEqualTo(rawDate); - assertThat(BigQueryTypeCoercionUtility.convertTimeWithCalendar(rawTime, null)) - .isEqualTo(rawTime); - assertThat(BigQueryTypeCoercionUtility.convertTimestampWithCalendar(rawTimestamp, null)) - .isEqualTo(rawTimestamp); - - // UTC Calendar shifts wall-clock components into target timezone - Calendar utcCal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - Date utcConvertedDate = BigQueryTypeCoercionUtility.convertDateWithCalendar(rawDate, utcCal); - assertThat(utcConvertedDate).isNotNull(); - - Time utcConvertedTime = BigQueryTypeCoercionUtility.convertTimeWithCalendar(rawTime, utcCal); - assertThat(utcConvertedTime).isNotNull(); - - Timestamp utcConvertedTimestamp = - BigQueryTypeCoercionUtility.convertTimestampWithCalendar(rawTimestamp, utcCal); - assertThat(utcConvertedTimestamp).isNotNull(); - assertThat(utcConvertedTimestamp.getNanos()).isEqualTo(123456789); - } -} diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/NullHandlingTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/NullHandlingTest.java deleted file mode 100644 index 2bff0e17e23b..000000000000 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/NullHandlingTest.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.cloud.bigquery.jdbc; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.jupiter.api.Test; - -public class NullHandlingTest { - - @Test - public void shouldReturnNullForNullByDefault() { - assertThat(BigQueryTypeCoercer.INSTANCE.coerceTo(Integer.class, null)).isNull(); - } - - @Test - public void shouldReturnCustomValueForNull() { - BigQueryTypeCoercer bigQueryTypeCoercer = - new BigQueryTypeCoercerBuilder().registerTypeCoercion(new NullToIntegerCoercion()).build(); - - assertThat(bigQueryTypeCoercer.coerceTo(Integer.class, null)).isEqualTo(0); - } - - private static class NullToIntegerCoercion implements BigQueryCoercion { - @Override - public Integer coerce(Void value) { - return 0; // returning zero as the default value - } - } -} From f2bfa63ef15ed799a00f295c6157268277a65c68 Mon Sep 17 00:00:00 2001 From: Neenu1995 Date: Tue, 1 Sep 2026 15:09:37 -0400 Subject: [PATCH 2/2] use type registry --- .../bigquery/jdbc/BigQueryBaseResultSet.java | 31 +++++++++++++++---- .../jdbc/BigQueryTemporalUtility.java | 3 ++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseResultSet.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseResultSet.java index f20e337265b8..de137e57933e 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseResultSet.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryBaseResultSet.java @@ -48,6 +48,7 @@ import java.sql.Statement; import java.sql.Time; import java.sql.Timestamp; +import java.time.ZoneId; import java.util.Calendar; import java.util.List; @@ -551,22 +552,40 @@ public InputStream getBinaryStream(int columnIndex) throws SQLException { @Override public Date getDate(int columnIndex, Calendar cal) throws SQLException { LOG.finestTrace("getDate"); - Date date = getDate(columnIndex); - return BigQueryTemporalUtility.convertDateWithCalendar(date, cal); + try { + Object value = getObject(columnIndex); + StandardSQLTypeName bqType = getStandardSQLTypeName(columnIndex); + ZoneId zone = (cal == null) ? null : cal.getTimeZone().toZoneId(); + return BigQueryTypeRegistry.convert(value, bqType, Date.class, zone); + } catch (BigQueryJdbcException e) { + throw createCoercionException(columnIndex, Date.class, e); + } } @Override public Time getTime(int columnIndex, Calendar cal) throws SQLException { LOG.finestTrace("getTime"); - Time time = getTime(columnIndex); - return BigQueryTemporalUtility.convertTimeWithCalendar(time, cal); + try { + Object value = getObject(columnIndex); + StandardSQLTypeName bqType = getStandardSQLTypeName(columnIndex); + ZoneId zone = (cal == null) ? null : cal.getTimeZone().toZoneId(); + return BigQueryTypeRegistry.convert(value, bqType, Time.class, zone); + } catch (BigQueryJdbcException e) { + throw createCoercionException(columnIndex, Time.class, e); + } } @Override public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException { LOG.finestTrace("getTimestamp"); - Timestamp timestamp = getTimestamp(columnIndex); - return BigQueryTemporalUtility.convertTimestampWithCalendar(timestamp, cal); + try { + Object value = getObject(columnIndex); + StandardSQLTypeName bqType = getStandardSQLTypeName(columnIndex); + ZoneId zone = (cal == null) ? null : cal.getTimeZone().toZoneId(); + return BigQueryTypeRegistry.convert(value, bqType, Timestamp.class, zone); + } catch (BigQueryJdbcException e) { + throw createCoercionException(columnIndex, Timestamp.class, e); + } } @Override diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTemporalUtility.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTemporalUtility.java index 5cfb4ff806fd..8880f1d1e623 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTemporalUtility.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryTemporalUtility.java @@ -231,6 +231,9 @@ static Time convertTimeWithCalendar(Time time, Calendar cal) { defaultCal.setTime(time); Calendar targetCal = getSafeCalendar(cal); + targetCal.set(Calendar.YEAR, 1970); + targetCal.set(Calendar.MONTH, Calendar.JANUARY); + targetCal.set(Calendar.DAY_OF_MONTH, 1); 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));