diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcTypeMappings.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcTypeMappings.java index 648cf8fd4673..913cfb181bed 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcTypeMappings.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcTypeMappings.java @@ -28,6 +28,12 @@ import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; import java.util.AbstractMap.SimpleEntry; import java.util.Map; @@ -103,35 +109,56 @@ static StandardSQLTypeName classToType(Class type) throws BigQueryJdbcSqlFeatureNotSupportedException { if (Boolean.class.isAssignableFrom(type)) { return StandardSQLTypeName.BOOL; - } else if (String.class.isAssignableFrom(type)) { + } + if (String.class.isAssignableFrom(type)) { return StandardSQLTypeName.STRING; - } else if (Integer.class.isAssignableFrom(type)) { + } + if (Integer.class.isAssignableFrom(type)) { return StandardSQLTypeName.INT64; - } else if (Long.class.isAssignableFrom(type)) { + } + if (Long.class.isAssignableFrom(type)) { return StandardSQLTypeName.INT64; - } else if (Short.class.isAssignableFrom(type)) { + } + if (Short.class.isAssignableFrom(type)) { return StandardSQLTypeName.INT64; - } else if (Double.class.isAssignableFrom(type)) { + } + if (Double.class.isAssignableFrom(type)) { return StandardSQLTypeName.FLOAT64; - } else if (Float.class.isAssignableFrom(type)) { + } + if (Float.class.isAssignableFrom(type)) { return StandardSQLTypeName.FLOAT64; - } else if (BigDecimal.class.isAssignableFrom(type)) { + } + if (BigDecimal.class.isAssignableFrom(type)) { return StandardSQLTypeName.NUMERIC; - } else if (Date.class.isAssignableFrom(type)) { + } + if (Date.class.isAssignableFrom(type) || LocalDate.class.isAssignableFrom(type)) { return StandardSQLTypeName.DATE; - } else if (Timestamp.class.isAssignableFrom(type)) { + } + 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; - } else if (Time.class.isAssignableFrom(type)) { + } + if (Time.class.isAssignableFrom(type) || LocalTime.class.isAssignableFrom(type)) { return StandardSQLTypeName.TIME; - } else if (JsonObject.class.isAssignableFrom(type)) { + } + if (JsonObject.class.isAssignableFrom(type)) { return StandardSQLTypeName.JSON; - } else if (Byte.class.isAssignableFrom(type)) { + } + if (Byte.class.isAssignableFrom(type)) { return StandardSQLTypeName.INT64; - } else if (Array.class.isAssignableFrom(type)) { + } + if (Array.class.isAssignableFrom(type)) { return StandardSQLTypeName.ARRAY; - } else if (Struct.class.isAssignableFrom(type)) { + } + if (Struct.class.isAssignableFrom(type)) { return StandardSQLTypeName.STRUCT; - } else if (byte[].class.isAssignableFrom(type)) { + } + if (byte[].class.isAssignableFrom(type)) { return StandardSQLTypeName.BYTES; } throw new BigQueryJdbcSqlFeatureNotSupportedException( diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryParameterMetaData.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryParameterMetaData.java new file mode 100644 index 000000000000..d2b5dd4edae9 --- /dev/null +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryParameterMetaData.java @@ -0,0 +1,186 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.bigquery.jdbc; + +import com.google.cloud.bigquery.StandardSQLTypeName; +import com.google.cloud.bigquery.exception.BigQueryJdbcException; +import java.sql.ParameterMetaData; +import java.sql.SQLException; +import java.sql.Types; + +/** ParameterMetaData implementation for BigQuery JDBC PreparedStatement parameters. */ +class BigQueryParameterMetaData implements ParameterMetaData { + private final int parameterCount; + private final BigQueryParameterHandler parameterHandler; + + BigQueryParameterMetaData(int parameterCount, BigQueryParameterHandler parameterHandler) { + this.parameterCount = parameterCount; + this.parameterHandler = parameterHandler; + } + + private void checkValidIndex(int param) throws SQLException { + if (param < 1 || param > this.parameterCount) { + throw new BigQueryJdbcException("Invalid parameter index: " + param); + } + } + + @Override + public int getParameterCount() { + return this.parameterCount; + } + + @Override + public int isNullable(int param) throws SQLException { + checkValidIndex(param); + return parameterNullable; + } + + @Override + public boolean isSigned(int param) throws SQLException { + int type = getParameterType(param); + return type == Types.SMALLINT + || type == Types.TINYINT + || type == Types.INTEGER + || type == Types.BIGINT + || type == Types.FLOAT + || type == Types.DOUBLE + || type == Types.DECIMAL + || type == Types.NUMERIC; + } + + @Override + public int getPrecision(int param) throws SQLException { + checkValidIndex(param); + StandardSQLTypeName sqlType = getStandardSQLTypeName(param); + if (sqlType == null) { + return 0; + } + BigQueryJdbcTypeMappings.ColumnTypeInfo typeInfo = + BigQueryJdbcTypeMappings.STANDARD_TYPE_INFO.get(sqlType); + if (typeInfo != null && typeInfo.columnSize != null) { + return typeInfo.columnSize; + } + return 0; + } + + @Override + public int getScale(int param) throws SQLException { + checkValidIndex(param); + StandardSQLTypeName sqlType = getStandardSQLTypeName(param); + if (sqlType == null) { + return 0; + } + BigQueryJdbcTypeMappings.ColumnTypeInfo typeInfo = + BigQueryJdbcTypeMappings.STANDARD_TYPE_INFO.get(sqlType); + if (typeInfo != null && typeInfo.decimalDigits != null) { + return typeInfo.decimalDigits; + } + return 0; + } + + @Override + public int getParameterType(int param) throws SQLException { + checkValidIndex(param); + StandardSQLTypeName sqlType = getStandardSQLTypeName(param); + if (sqlType == null) { + return Types.OTHER; + } + Integer jdbcType = BigQueryJdbcTypeMappings.standardSQLToJavaSqlTypesMapping.get(sqlType); + if (jdbcType != null) { + return jdbcType; + } + return Types.OTHER; + } + + @Override + public String getParameterTypeName(int param) throws SQLException { + checkValidIndex(param); + StandardSQLTypeName sqlType = getStandardSQLTypeName(param); + return sqlType != null ? sqlType.name() : "UNKNOWN"; + } + + @Override + public String getParameterClassName(int param) throws SQLException { + checkValidIndex(param); + StandardSQLTypeName sqlType = getStandardSQLTypeName(param); + if (sqlType != null) { + Class clazz = BigQueryJdbcTypeMappings.standardSQLToJavaTypeMapping.get(sqlType); + if (clazz != null) { + return clazz.getName(); + } + } + if (this.parameterHandler == null) { + return Object.class.getName(); + } + Class boundType = this.parameterHandler.getType(param); + if (boundType != null) { + return boundType.getName(); + } + return Object.class.getName(); + } + + @Override + public int getParameterMode(int param) throws SQLException { + checkValidIndex(param); + if (this.parameterHandler == null) { + return parameterModeIn; + } + BigQueryParameterHandler.BigQueryStatementParameterType paramType = + this.parameterHandler.getParameterType(param); + if (paramType == BigQueryParameterHandler.BigQueryStatementParameterType.OUT) { + return parameterModeOut; + } + if (paramType == BigQueryParameterHandler.BigQueryStatementParameterType.INOUT) { + return parameterModeInOut; + } + return parameterModeIn; + } + + private StandardSQLTypeName getStandardSQLTypeName(int param) { + if (this.parameterHandler == null) { + return null; + } + StandardSQLTypeName sqlType = this.parameterHandler.getSqlType(param); + if (sqlType != null) { + return sqlType; + } + Class javaType = this.parameterHandler.getType(param); + if (javaType == null) { + return null; + } + try { + return BigQueryJdbcTypeMappings.classToType(javaType); + } catch (SQLException ignored) { + // fall back to default + return null; + } + } + + @Override + public T unwrap(Class iface) throws SQLException { + if (!isWrapperFor(iface)) { + throw new BigQueryJdbcException( + "Cannot unwrap to " + (iface != null ? iface.getName() : "null")); + } + return iface.cast(this); + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return iface != null && iface.isInstance(this); + } +}