Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import com.google.cloud.bigquery.TableDefinition;
import com.google.cloud.bigquery.TableId;
import com.google.cloud.bigquery.exception.BigQueryJdbcException;
import com.google.common.collect.ImmutableMap;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
Expand All @@ -59,6 +60,7 @@
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Scanner;
import java.util.Set;
Expand Down Expand Up @@ -1650,8 +1652,8 @@ ColumnTypeInfo determineTypeInfoFromDataType(
String columnName,
int ordinalPosition) {

ColumnTypeInfo defaultVarcharTypeInfo =
new ColumnTypeInfo(Types.VARCHAR, "VARCHAR", null, null, null);
ColumnTypeInfo defaultStringTypeInfo =
new ColumnTypeInfo(Types.NVARCHAR, "STRING", null, null, null);
try {
String typeKind = argumentDataType.getTypeKind();
if (typeKind != null && !typeKind.isEmpty()) {
Expand All @@ -1664,10 +1666,10 @@ ColumnTypeInfo determineTypeInfoFromDataType(
} catch (Exception e) {
LOG.warning(
"Proc: %s, Arg: %s (Pos %d) - Caught an unexpected Exception during type"
+ " determination. Defaulting type to VARCHAR. Error: %s",
+ " determination. Defaulting type to STRING. Error: %s",
procedureName, columnName, ordinalPosition, e.getMessage());
}
return defaultVarcharTypeInfo;
return defaultStringTypeInfo;
}

Comparator<FieldValueList> defineGetProcedureColumnsComparator(FieldList resultSchemaFields) {
Expand Down Expand Up @@ -4750,13 +4752,16 @@ public boolean generatedKeyAlwaysReturned() {
}

@Override
public <T> T unwrap(Class<T> iface) {
return null;
public <T> T unwrap(Class<T> iface) throws SQLException {
if (iface.isInstance(this)) {
return iface.cast(this);
}
throw new SQLException("Cannot unwrap to " + iface.getName());
}

@Override
public boolean isWrapperFor(Class<?> iface) {
return false;
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return iface != null && iface.isInstance(this);
}

// --- Helper Methods ---
Expand Down Expand Up @@ -4828,47 +4833,58 @@ private Tuple<String, String> determineEffectiveCatalogAndSchema(
return Tuple.of(effectiveCatalog, effectiveSchemaPattern);
}

// BigQuery STRING represents Unicode character strings (UTF-8).
// Standard JDBC maps UTF-8/Unicode data to Types.NVARCHAR rather than Types.VARCHAR.
private static final Map<StandardSQLTypeName, ColumnTypeInfo> STANDARD_TYPE_INFO =
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add a test to ensure that metadata & resultsetmetadata column types match each other?

ImmutableMap.<StandardSQLTypeName, ColumnTypeInfo>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", 29, null, null))
.put(
StandardSQLTypeName.DATETIME,
new ColumnTypeInfo(Types.TIMESTAMP, "DATETIME", 29, null, null))
.put(StandardSQLTypeName.DATE, new ColumnTypeInfo(Types.DATE, "DATE", 10, null, null))
.put(StandardSQLTypeName.TIME, new ColumnTypeInfo(Types.TIME, "TIME", 15, null, 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();

private ColumnTypeInfo getColumnTypeInfoForSqlType(StandardSQLTypeName bqType) {
if (bqType == null) {
LOG.warning("Null BigQuery type encountered: " + bqType.name() + ". Mapping to VARCHAR.");
return new ColumnTypeInfo(Types.VARCHAR, bqType.name(), null, null, null);
LOG.warning("Null BigQuery type encountered. Mapping to STRING.");
return new ColumnTypeInfo(Types.NVARCHAR, "STRING", null, null, null);
}

switch (bqType) {
case INT64:
return new ColumnTypeInfo(Types.BIGINT, "BIGINT", 19, 0, 10);
case BOOL:
return new ColumnTypeInfo(Types.BOOLEAN, "BOOLEAN", 1, null, null);
case FLOAT64:
return new ColumnTypeInfo(Types.DOUBLE, "DOUBLE", 15, null, 10);
case NUMERIC:
return new ColumnTypeInfo(Types.NUMERIC, "NUMERIC", 38, 9, 10);
case BIGNUMERIC:
return new ColumnTypeInfo(Types.NUMERIC, "NUMERIC", 77, 38, 10);
case STRING:
return new ColumnTypeInfo(Types.NVARCHAR, "NVARCHAR", null, null, null);
case TIMESTAMP:
case DATETIME:
return new ColumnTypeInfo(Types.TIMESTAMP, "TIMESTAMP", 29, null, null);
case DATE:
return new ColumnTypeInfo(Types.DATE, "DATE", 10, null, null);
case TIME:
return new ColumnTypeInfo(Types.TIME, "TIME", 15, null, null);
case GEOGRAPHY:
return new ColumnTypeInfo(Types.OTHER, "GEOGRAPHY", null, null, null);
case JSON:
return new ColumnTypeInfo(Types.OTHER, "JSON", null, null, null);
case INTERVAL:
return new ColumnTypeInfo(Types.OTHER, "INTERVAL", null, null, null);
case BYTES:
return new ColumnTypeInfo(Types.VARBINARY, "VARBINARY", null, null, null);
case STRUCT:
return new ColumnTypeInfo(Types.STRUCT, "STRUCT", null, null, null);
default:
LOG.warning(
"Unknown BigQuery type encountered: " + bqType.name() + ". Mapping to VARCHAR.");
return new ColumnTypeInfo(Types.VARCHAR, bqType.name(), null, null, null);
ColumnTypeInfo info = STANDARD_TYPE_INFO.get(bqType);
if (info == null) {
LOG.warning("Unknown BigQuery type encountered: " + bqType.name() + ". Mapping to STRING.");
return new ColumnTypeInfo(Types.NVARCHAR, "STRING", null, null, null);
}
return info;
}

<T> List<T> findMatchingBigQueryObjects(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
// Support standard JDBC Wrapper unwrap operations
if (methodName.equals("unwrap") && args != null && args.length == 1) {
Class<?> iface = (Class<?>) args[0];
if (iface.isInstance(target)) {
if (iface != null && iface.isInstance(target)) {
return target;
}
try {
Expand All @@ -120,7 +120,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
}
if (methodName.equals("isWrapperFor") && args != null && args.length == 1) {
Class<?> iface = (Class<?>) args[0];
if (iface.isInstance(target)) {
if (iface != null && iface.isInstance(target)) {
return true;
}
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import com.google.cloud.bigquery.Field.Mode;
import com.google.cloud.bigquery.FieldList;
import com.google.cloud.bigquery.StandardSQLTypeName;
import com.google.cloud.bigquery.exception.BigQueryJdbcSqlFeatureNotSupportedException;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
Expand Down Expand Up @@ -74,8 +73,7 @@ public boolean isCaseSensitive(int column) {

@Override
public boolean isSearchable(int column) {
int colType = getColumnType(column);
return colType != Types.OTHER;
return true;
}

@Override
Expand Down Expand Up @@ -205,14 +203,17 @@ public String getColumnClassName(int column) {
.getName();
}

// Unsupported methods:
// Wrapper methods:
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
throw new BigQueryJdbcSqlFeatureNotSupportedException("unwrap is not implemented");
if (iface.isInstance(this)) {
return iface.cast(this);
}
throw new SQLException("Cannot unwrap to " + iface.getName());
}

@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
throw new BigQueryJdbcSqlFeatureNotSupportedException("isWrapperFor is not implemented");
return iface != null && iface.isInstance(this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ public void testMapBigQueryTypeToJdbc_ScalarTypes() {
BigQueryDatabaseMetaData.ColumnTypeInfo infoInt64 =
dbMetadata.mapBigQueryTypeToJdbc(fieldInt64);
assertEquals(Types.BIGINT, infoInt64.jdbcType);
assertEquals("BIGINT", infoInt64.typeName);
assertEquals("INT64", infoInt64.typeName);
assertEquals(Integer.valueOf(19), infoInt64.columnSize);
assertEquals(Integer.valueOf(0), infoInt64.decimalDigits);
assertEquals(Integer.valueOf(10), infoInt64.numPrecRadix);
Expand All @@ -264,7 +264,7 @@ public void testMapBigQueryTypeToJdbc_ScalarTypes() {
BigQueryDatabaseMetaData.ColumnTypeInfo infoString =
dbMetadata.mapBigQueryTypeToJdbc(fieldString);
assertEquals(Types.NVARCHAR, infoString.jdbcType);
assertEquals("NVARCHAR", infoString.typeName);
assertEquals("STRING", infoString.typeName);
assertNull(infoString.columnSize);
assertNull(infoString.decimalDigits);
assertNull(infoString.numPrecRadix);
Expand All @@ -276,7 +276,7 @@ public void testMapBigQueryTypeToJdbc_ScalarTypes() {
.build();
BigQueryDatabaseMetaData.ColumnTypeInfo infoBool = dbMetadata.mapBigQueryTypeToJdbc(fieldBool);
assertEquals(Types.BOOLEAN, infoBool.jdbcType);
assertEquals("BOOLEAN", infoBool.typeName);
assertEquals("BOOL", infoBool.typeName);
assertEquals(Integer.valueOf(1), infoBool.columnSize);

// BYTES -> VARBINARY
Expand All @@ -287,7 +287,7 @@ public void testMapBigQueryTypeToJdbc_ScalarTypes() {
BigQueryDatabaseMetaData.ColumnTypeInfo infoBytes =
dbMetadata.mapBigQueryTypeToJdbc(fieldBytes);
assertEquals(Types.VARBINARY, infoBytes.jdbcType);
assertEquals("VARBINARY", infoBytes.typeName);
assertEquals("BYTES", infoBytes.typeName);
assertNull(infoBytes.columnSize);

// TIMESTAMP -> TIMESTAMP
Expand All @@ -311,7 +311,7 @@ public void testMapBigQueryTypeToJdbc_ScalarTypes() {
BigQueryDatabaseMetaData.ColumnTypeInfo infoDateTime =
dbMetadata.mapBigQueryTypeToJdbc(fieldDateTime);
assertEquals(Types.TIMESTAMP, infoDateTime.jdbcType);
assertEquals("TIMESTAMP", infoDateTime.typeName);
assertEquals("DATETIME", infoDateTime.typeName);
assertEquals(Integer.valueOf(29), infoDateTime.columnSize);
assertNull(infoDateTime.decimalDigits);
assertNull(infoDateTime.numPrecRadix);
Expand All @@ -337,7 +337,7 @@ public void testMapBigQueryTypeToJdbc_ScalarTypes() {
BigQueryDatabaseMetaData.ColumnTypeInfo infoBigNumeric =
dbMetadata.mapBigQueryTypeToJdbc(fieldBigNumeric);
assertEquals(Types.NUMERIC, infoBigNumeric.jdbcType);
assertEquals("NUMERIC", infoBigNumeric.typeName);
assertEquals("BIGNUMERIC", infoBigNumeric.typeName);
assertEquals(Integer.valueOf(77), infoBigNumeric.columnSize);
assertEquals(Integer.valueOf(38), infoBigNumeric.decimalDigits);
assertEquals(Integer.valueOf(10), infoBigNumeric.numPrecRadix);
Expand Down Expand Up @@ -445,7 +445,7 @@ public void testCreateColumnRow() {
assertEquals(table, row.get(2).getStringValue()); // 3. TABLE_NAME
assertEquals("user_name", row.get(3).getStringValue()); // 4. COLUMN_NAME
assertEquals(String.valueOf(Types.NVARCHAR), row.get(4).getStringValue()); // 5. DATA_TYPE
assertEquals("NVARCHAR", row.get(5).getStringValue()); // 6. TYPE_NAME
assertEquals("STRING", row.get(5).getStringValue()); // 6. TYPE_NAME
assertTrue(row.get(6).isNull()); // 7. COLUMN_SIZE (was null for STRING)
assertTrue(row.get(7).isNull()); // 8. BUFFER_LENGTH (always null)
assertTrue(row.get(8).isNull()); // 9. DECIMAL_DIGITS (null for STRING)
Expand Down Expand Up @@ -480,7 +480,7 @@ public void testCreateColumnRow_RequiredInt() {
assertEquals(24, row.size());
assertEquals("user_id", row.get(3).getStringValue()); // COLUMN_NAME
assertEquals(String.valueOf(Types.BIGINT), row.get(4).getStringValue()); // DATA_TYPE
assertEquals("BIGINT", row.get(5).getStringValue()); // TYPE_NAME
assertEquals("INT64", row.get(5).getStringValue()); // TYPE_NAME
assertEquals("19", row.get(6).getStringValue()); // COLUMN_SIZE
assertEquals("0", row.get(8).getStringValue()); // DECIMAL_DIGITS
assertEquals("10", row.get(9).getStringValue()); // NUM_PREC_RADIX
Expand Down Expand Up @@ -1160,21 +1160,21 @@ public void testDetermineTypeInfoFromDataType() {
BigQueryDatabaseMetaData.ColumnTypeInfo infoInt64 =
dbMetadata.determineTypeInfoFromDataType(sqlInt64, "p", "c", 1);
assertEquals(Types.BIGINT, infoInt64.jdbcType);
assertEquals("BIGINT", infoInt64.typeName);
assertEquals("INT64", infoInt64.typeName);

// STRING
StandardSQLDataType sqlString = mockStandardSQLDataType(StandardSQLTypeName.STRING);
BigQueryDatabaseMetaData.ColumnTypeInfo infoString =
dbMetadata.determineTypeInfoFromDataType(sqlString, "p", "c", 1);
assertEquals(Types.NVARCHAR, infoString.jdbcType);
assertEquals("NVARCHAR", infoString.typeName);
assertEquals("STRING", infoString.typeName);

// BOOL
StandardSQLDataType sqlBool = mockStandardSQLDataType(StandardSQLTypeName.BOOL);
BigQueryDatabaseMetaData.ColumnTypeInfo infoBool =
dbMetadata.determineTypeInfoFromDataType(sqlBool, "p", "c", 1);
assertEquals(Types.BOOLEAN, infoBool.jdbcType);
assertEquals("BOOLEAN", infoBool.typeName);
assertEquals("BOOL", infoBool.typeName);

// STRUCT
StandardSQLDataType sqlStruct = mockStandardSQLDataType(StandardSQLTypeName.STRUCT);
Expand All @@ -1183,21 +1183,21 @@ public void testDetermineTypeInfoFromDataType() {
assertEquals(Types.STRUCT, infoStruct.jdbcType);
assertEquals("STRUCT", infoStruct.typeName);

// Case: null typeKind from StandardSQLDataType (should default to VARCHAR)
// Case: null typeKind from StandardSQLDataType (should default to STRING)
StandardSQLDataType sqlNullKind = mock(StandardSQLDataType.class);
when(sqlNullKind.getTypeKind()).thenReturn(null);
BigQueryDatabaseMetaData.ColumnTypeInfo infoNullKind =
dbMetadata.determineTypeInfoFromDataType(sqlNullKind, "p", "c", 1);
assertEquals(Types.VARCHAR, infoNullKind.jdbcType);
assertEquals("VARCHAR", infoNullKind.typeName);
assertEquals(Types.NVARCHAR, infoNullKind.jdbcType);
assertEquals("STRING", infoNullKind.typeName);

// Case: unknown typeKind from StandardSQLDataType (should default to VARCHAR)
// Case: unknown typeKind from StandardSQLDataType (should default to STRING)
StandardSQLDataType sqlUnknownKind = mock(StandardSQLDataType.class);
when(sqlUnknownKind.getTypeKind()).thenReturn("SUPER_DOOPER_TYPE");
BigQueryDatabaseMetaData.ColumnTypeInfo infoUnknownKind =
dbMetadata.determineTypeInfoFromDataType(sqlUnknownKind, "p", "c", 1);
assertEquals(Types.VARCHAR, infoUnknownKind.jdbcType);
assertEquals("VARCHAR", infoUnknownKind.typeName);
assertEquals(Types.NVARCHAR, infoUnknownKind.jdbcType);
assertEquals("STRING", infoUnknownKind.typeName);
}

@Test
Expand Down Expand Up @@ -1225,7 +1225,7 @@ public void testCreateProcedureColumnRow_BasicInParam() {
String.valueOf(DatabaseMetaData.procedureColumnIn),
row.get(4).getStringValue()); // 5. COLUMN_TYPE
assertEquals(String.valueOf(Types.NVARCHAR), row.get(5).getStringValue()); // 6. DATA_TYPE
assertEquals("NVARCHAR", row.get(6).getStringValue()); // 7. TYPE_NAME
assertEquals("STRING", row.get(6).getStringValue()); // 7. TYPE_NAME
assertTrue(row.get(7).isNull()); // 8. PRECISION
assertTrue(row.get(8).isNull()); // 9. LENGTH
assertTrue(row.get(9).isNull()); // 10. SCALE
Expand Down Expand Up @@ -2706,7 +2706,7 @@ public void testCreateFunctionColumnRow() {
assertEquals("param_in", row.get(3).getStringValue());
assertEquals(String.valueOf(DatabaseMetaData.functionColumnIn), row.get(4).getStringValue());
assertEquals(String.valueOf(Types.NVARCHAR), row.get(5).getStringValue()); // DATA_TYPE
assertEquals("NVARCHAR", row.get(6).getStringValue()); // TYPE_NAME
assertEquals("STRING", row.get(6).getStringValue()); // TYPE_NAME
assertTrue(row.get(7).isNull()); // PRECISION
assertTrue(row.get(8).isNull()); // LENGTH
assertTrue(row.get(9).isNull()); // SCALE
Expand Down Expand Up @@ -3228,6 +3228,24 @@ public void testGetSQLStateType() throws SQLException {
assertEquals(DatabaseMetaData.sqlStateSQL, dbMetadata.getSQLStateType());
}

@Test
public void testWrapperMethods() throws SQLException {
assertTrue(dbMetadata.isWrapperFor(DatabaseMetaData.class));
assertTrue(dbMetadata.isWrapperFor(BigQueryDatabaseMetaData.class));
assertFalse(dbMetadata.isWrapperFor(java.sql.Connection.class));
assertFalse(dbMetadata.isWrapperFor(null));

assertSame(dbMetadata, dbMetadata.unwrap(DatabaseMetaData.class));
assertSame(dbMetadata, dbMetadata.unwrap(BigQueryDatabaseMetaData.class));

try {
dbMetadata.unwrap(java.sql.Connection.class);
org.junit.jupiter.api.Assertions.fail("Should have thrown SQLException");
} catch (SQLException e) {
assertTrue(e.getMessage().contains("Cannot unwrap to java.sql.Connection"));
}
}

@Test
public void testMetadataMethodsDoNotInterfere() throws SQLException {
Statement mockStatement1 = mock(Statement.class);
Expand Down
Loading
Loading