Skip to content

refactor(jdbc): migrate write path and metadata to TypeRegistry - #14061

Open
Neenu1995 wants to merge 3 commits into
mainfrom
jdbc-phase4-registry-integration
Open

refactor(jdbc): migrate write path and metadata to TypeRegistry#14061
Neenu1995 wants to merge 3 commits into
mainfrom
jdbc-phase4-registry-integration

Conversation

@Neenu1995

@Neenu1995 Neenu1995 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

b/538177258

This PR executes Part 1 of Phase 4 of the Type Registry consolidation.
It migrates all parameter binding (Write Path) and metadata sizing away from the legacy `BigQueryJdbcTypeMappings` and onto the unified `BigQueryTypeRegistry`.

Changes:

  • Write Path: Re-routed parameter SQL type binding in `BigQueryParameterHandler`, `BigQueryPreparedStatement`, and `BigQueryCallableStatement` to use the registry.
  • Metadata Integration: Migrated `ColumnTypeInfo` metadata map into `BigQueryTypeRegistry`.
  • Metadata Classes: Updated `BigQueryResultSetMetadata`, `BigQueryParameterMetaData`, and `BigQueryDatabaseMetaData` to extract sizes and types exclusively from the registry.

@Neenu1995
Neenu1995 requested review from a team as code owners August 12, 2026 23:58

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request refactors the BigQuery JDBC driver by migrating type mapping logic from BigQueryJdbcTypeMappings to BigQueryTypeRegistry across several classes, including BigQueryCallableStatement, BigQueryParameterMetaData, and BigQueryResultSetMetadata. The review feedback highlights several critical improvement opportunities: correcting misplaced Javadoc comments in BigQueryTypeRegistry, resolving dead code caused by a redundant null check on a primitive return value in BigQueryParameterMetaData, and addressing lossy type conversions where converting to a JDBC type first before mapping to a Java class loses precision for types like GEOGRAPHY or JSON. It is recommended to introduce a direct toJavaClass(StandardSQLTypeName) method to preserve type safety and prevent potential null pointer exceptions.

Comment on lines 364 to 375
/** Returns the default Java target class for a given JDBC type constant. */
/** Returns the JDBC Type constant for a given BigQuery type. */
public static int toJdbcType(StandardSQLTypeName bqType) {
if (bqType == null) return java.sql.Types.OTHER;
int ordinal = bqType.ordinal();
if (ordinal >= DESCRIPTORS_BY_ORDINAL.length || DESCRIPTORS_BY_ORDINAL[ordinal] == null) {
return java.sql.Types.OTHER;
}
return DESCRIPTORS_BY_ORDINAL[ordinal].getJdbcType();
}

public static Class<?> toJavaClass(int jdbcType) {

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.

medium

The Javadoc comments here are misplaced. The Javadoc for toJavaClass(int) (/** Returns the default Java target class for a given JDBC type constant. */) has been left above toJdbcType, resulting in double Javadocs for toJdbcType and none for toJavaClass(int).

Additionally, we can introduce a non-lossy toJavaClass(StandardSQLTypeName) method to avoid converting StandardSQLTypeName to a JDBC int type first (which loses precision for types like GEOGRAPHY, JSON, INTERVAL, and RANGE that all map to Types.OTHER).

  /** Returns the JDBC Type constant for a given BigQuery type. */
  public static int toJdbcType(StandardSQLTypeName bqType) {
    if (bqType == null) return Types.OTHER;
    int ordinal = bqType.ordinal();
    if (ordinal >= DESCRIPTORS_BY_ORDINAL.length || DESCRIPTORS_BY_ORDINAL[ordinal] == null) {
      return Types.OTHER;
    }
    return DESCRIPTORS_BY_ORDINAL[ordinal].getJdbcType();
  }

  /** Returns the default Java target class for a given BigQuery type. */
  public static Class<?> toJavaClass(StandardSQLTypeName bqType) {
    if (bqType == null) return null;
    int ordinal = bqType.ordinal();
    if (ordinal >= DESCRIPTORS_BY_ORDINAL.length || DESCRIPTORS_BY_ORDINAL[ordinal] == null) {
      return null;
    }
    return DESCRIPTORS_BY_ORDINAL[ordinal].getJavaClass();
  }

  /** Returns the default Java target class for a given JDBC type constant. */
  public static Class<?> toJavaClass(int jdbcType) {
References
  1. Update stale Javadoc comments to reflect new method behavior for improved clarity, rather than removing them.
  2. Do not use fully qualified class names (such as java.time.Duration) if there is no class name conflict in the file and the class is already imported, as it unnecessarily reduces code readability.

StandardSQLTypeName sqlType = getStandardSQLTypeName(param);
if (sqlType != null) {
Class<?> clazz = BigQueryJdbcTypeMappings.standardSQLToJavaTypeMapping.get(sqlType);
Class<?> clazz = BigQueryTypeRegistry.toJavaClass(BigQueryTypeRegistry.toJdbcType(sqlType));

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.

medium

Using BigQueryTypeRegistry.toJavaClass(BigQueryTypeRegistry.toJdbcType(sqlType)) is lossy because multiple distinct BigQuery types (such as GEOGRAPHY, JSON, INTERVAL, and RANGE) all map to Types.OTHER. Converting them to a JDBC type first and then to a Java class loses the specific type information and can return null or incorrect classes.

Instead, use the direct toJavaClass(StandardSQLTypeName) method.

Suggested change
Class<?> clazz = BigQueryTypeRegistry.toJavaClass(BigQueryTypeRegistry.toJdbcType(sqlType));
Class<?> clazz = BigQueryTypeRegistry.toJavaClass(sqlType);

Comment on lines 219 to 221
return BigQueryTypeRegistry.toJavaClass(
BigQueryTypeRegistry.toJdbcType(getStandardSQLTypeName(column)))
.getName();

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.

medium

Similarly, using BigQueryTypeRegistry.toJavaClass(BigQueryTypeRegistry.toJdbcType(...)) is lossy and can lead to NullPointerException if toJavaClass returns null.

Instead, use the direct toJavaClass(StandardSQLTypeName) method and add a safe null check.

    Class<?> clazz = BigQueryTypeRegistry.toJavaClass(getStandardSQLTypeName(column));
    return clazz != null ? clazz.getName() : null;

Comment on lines 97 to 103
if (sqlType == null) {
return Types.OTHER;
}
Integer jdbcType = BigQueryJdbcTypeMappings.standardSQLToJavaSqlTypesMapping.get(sqlType);
Integer jdbcType = BigQueryTypeRegistry.toJdbcType(sqlType);
if (jdbcType != null) {
return jdbcType;
}

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.

medium

Since BigQueryTypeRegistry.toJdbcType(sqlType) returns a primitive int, the local variable jdbcType (autoboxed to Integer) can never be null. The null check if (jdbcType != null) is dead code.

Furthermore, toJdbcType(null) already returns Types.OTHER by default, so we can simplify this entire block to a single return statement.

    return BigQueryTypeRegistry.toJdbcType(sqlType);

@Neenu1995

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request refactors the BigQuery JDBC driver's type mapping mechanism by migrating from BigQueryJdbcTypeMappings to a centralized BigQueryTypeRegistry. This change affects several classes, including BigQueryCallableStatement, BigQueryParameterMetaData, and BigQueryResultSetMetadata. The review feedback highlights several areas for improvement: first, expanding the JDBC_TO_JAVA_CLASS_MAP to include missing standard JDBC types (such as DECIMAL and CHAR) to prevent runtime exceptions; and second, removing redundant null checks and boxing operations across the codebase, as the newly introduced registry methods either return primitive types or are guaranteed to return non-null values.

Comment on lines +381 to +403
private static final Map<Integer, Class<?>> JDBC_TO_JAVA_CLASS_MAP =
ImmutableMap.<Integer, Class<?>>builder()
.put(Types.BIGINT, Long.class)
.put(Types.INTEGER, Integer.class)
.put(Types.SMALLINT, Short.class)
.put(Types.TINYINT, Byte.class)
.put(Types.BOOLEAN, Boolean.class)
.put(Types.DOUBLE, Double.class)
.put(Types.FLOAT, Float.class)
.put(Types.NUMERIC, BigDecimal.class)
.put(Types.VARCHAR, String.class)
.put(Types.NVARCHAR, String.class)
.put(Types.TIMESTAMP, Timestamp.class)
.put(Types.DATE, Date.class)
.put(Types.TIME, Time.class)
.put(Types.OTHER, String.class)
.put(Types.BINARY, byte[].class)
.put(Types.VARBINARY, byte[].class)
.put(Types.STRUCT, Struct.class)
.put(Types.BIT, Boolean.class)
.put(Types.ARRAY, Array.class)
.put(Types.NULL, String.class)
.build();

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.

high

The JDBC_TO_JAVA_CLASS_MAP is missing several common JDBC type mappings, such as Types.DECIMAL (which should map to BigDecimal.class), Types.CHAR (which should map to String.class), and potentially Types.REAL or Types.CLOB. Without these mappings, calling methods like setNull or registerOutParameter with these standard JDBC types will throw a BigQueryJdbcSqlFeatureNotSupportedException at runtime, which could break compatibility with existing ORMs or database tools.

  private static final Map<Integer, Class<?>> JDBC_TO_JAVA_CLASS_MAP =
      ImmutableMap.<Integer, Class<?>>builder()
          .put(Types.BIGINT, Long.class)
          .put(Types.INTEGER, Integer.class)
          .put(Types.SMALLINT, Short.class)
          .put(Types.TINYINT, Byte.class)
          .put(Types.BOOLEAN, Boolean.class)
          .put(Types.DOUBLE, Double.class)
          .put(Types.FLOAT, Float.class)
          .put(Types.REAL, Float.class)
          .put(Types.NUMERIC, BigDecimal.class)
          .put(Types.DECIMAL, BigDecimal.class)
          .put(Types.CHAR, String.class)
          .put(Types.VARCHAR, String.class)
          .put(Types.LONGVARCHAR, String.class)
          .put(Types.NCHAR, String.class)
          .put(Types.NVARCHAR, String.class)
          .put(Types.LONGNVARCHAR, String.class)
          .put(Types.CLOB, String.class)
          .put(Types.TIMESTAMP, Timestamp.class)
          .put(Types.DATE, Date.class)
          .put(Types.TIME, Time.class)
          .put(Types.OTHER, String.class)
          .put(Types.BINARY, byte[].class)
          .put(Types.VARBINARY, byte[].class)
          .put(Types.LONGVARBINARY, byte[].class)
          .put(Types.STRUCT, Struct.class)
          .put(Types.BIT, Boolean.class)
          .put(Types.ARRAY, Array.class)
          .put(Types.NULL, String.class)
          .build();

Comment on lines +781 to 784
Class<?> javaType = BigQueryTypeRegistry.toJavaClass(sqlType);
if (javaType == null) {
javaType = String.class;
}

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.

medium

Since BigQueryTypeRegistry.toJavaClass(int) throws a BigQueryJdbcSqlFeatureNotSupportedException when the JDBC type is unsupported instead of returning null, the variable javaType will never be null. The subsequent null check is dead code and can be safely removed.

Suggested change
Class<?> javaType = BigQueryTypeRegistry.toJavaClass(sqlType);
if (javaType == null) {
javaType = String.class;
}
Class<?> javaType = BigQueryTypeRegistry.toJavaClass(sqlType);
References
  1. Avoid adding defensive null checks or redundant null-handling logic for method return values that are guaranteed to be non-null by design, such as when an empty state or failure condition would already throw an exception earlier in the execution path.

Comment on lines +100 to 102
Integer jdbcType = BigQueryTypeRegistry.toJdbcType(sqlType);
if (jdbcType != null) {
return jdbcType;

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.

medium

Since BigQueryTypeRegistry.toJdbcType(sqlType) returns a primitive int (which can never be null), the Integer boxing and subsequent null check are redundant. We can simplify this by checking against Types.OTHER (the fallback value returned by toJdbcType when unmapped) to ensure the rest of the method remains reachable.

Suggested change
Integer jdbcType = BigQueryTypeRegistry.toJdbcType(sqlType);
if (jdbcType != null) {
return jdbcType;
int jdbcType = BigQueryTypeRegistry.toJdbcType(sqlType);
if (jdbcType != Types.OTHER) {
return jdbcType;
}
References
  1. Avoid adding defensive null checks for values that are guaranteed to be non-null by design, as this can hide invariant breaks.

Comment on lines +119 to 122
Class<?> clazz = BigQueryTypeRegistry.toJavaClass(sqlType);
if (clazz != null) {
return clazz.getName();
}

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.

medium

Since BigQueryTypeRegistry.toJavaClass(StandardSQLTypeName) always returns a non-null class (falling back to String.class if the type is unmapped or null), the null check if (clazz != null) is redundant and can be simplified.

Suggested change
Class<?> clazz = BigQueryTypeRegistry.toJavaClass(sqlType);
if (clazz != null) {
return clazz.getName();
}
return BigQueryTypeRegistry.toJavaClass(sqlType).getName();
References
  1. Avoid adding defensive null checks or redundant null-handling logic for method return values that are guaranteed to be non-null by design, such as when an empty state or failure condition would already throw an exception earlier in the execution path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant