From 742140447ea0dcbc1812a3e272fcdc71655e4200 Mon Sep 17 00:00:00 2001 From: Richeng Huang Date: Sat, 24 Oct 2026 00:25:53 +0000 Subject: [PATCH 1/2] feat(bigquery-jdbc): expose BigQueryResultSetMetadata and getField(int) --- .../cloud/bigquery/jdbc/BigQueryResultSetMetadata.java | 10 ++++++++-- .../bigquery/jdbc/BigQueryResultSetMetadataTest.java | 5 +++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadata.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadata.java index ab86cfdc9d17..4a8c48b119f8 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadata.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadata.java @@ -27,7 +27,7 @@ import java.sql.Types; /** This class returns ResultSetMetadata for the JSON and the Arrow ResultSets */ -class BigQueryResultSetMetadata implements ResultSetMetaData { +public class BigQueryResultSetMetadata implements ResultSetMetaData { private static final BigQueryJdbcResultSetLogger LOG = BigQueryJdbcResultSetLogger.getLogger(BigQueryResultSetMetadata.class); private final FieldList schemaFieldList; @@ -53,7 +53,13 @@ BigQueryStatement getStatement() { return this.statement; } - private Field getField(int sqlColumn) { + /** + * Returns the BigQuery {@link Field} schema definition for the given 1-based SQL column index. + * + * @param sqlColumn the first column is 1, the second is 2, ... + * @return the {@link Field} for the specified column + */ + public Field getField(int sqlColumn) { return this.schemaFieldList.get(sqlColumn - 1); } diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadataTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadataTest.java index 762c40cf8c2b..322b7e52db24 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadataTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadataTest.java @@ -271,6 +271,11 @@ public void testWrapperMethods() throws SQLException { assertThat(unwrappedImpl).isNotSameInstanceAs(resultSetMetaData); assertThat(unwrappedImpl).isInstanceOf(BigQueryResultSetMetadata.class); + BigQueryResultSetMetadata bqMetaData = + resultSetMetaData.unwrap(BigQueryResultSetMetadata.class); + assertThat(bqMetaData.getField(1)).isEqualTo(fieldList.get(0)); + assertThat(bqMetaData.getField(8).getMode()).isEqualTo(Field.Mode.REPEATED); + BigQueryJdbcException e = assertThrows( BigQueryJdbcException.class, () -> resultSetMetaData.unwrap(java.sql.Connection.class)); From 4966887670e0d1b984ffd719c1eef2252c75db4e Mon Sep 17 00:00:00 2001 From: Richeng Huang Date: Sat, 24 Oct 2026 00:35:14 +0000 Subject: [PATCH 2/2] fix(bigquery-jdbc): validate 1-based column index bounds in getField(int) --- .../jdbc/BigQueryResultSetMetadata.java | 30 +++++++++++-------- .../jdbc/BigQueryResultSetMetadataTest.java | 2 ++ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadata.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadata.java index 4a8c48b119f8..34dc1f903f75 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadata.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadata.java @@ -58,12 +58,16 @@ BigQueryStatement getStatement() { * * @param sqlColumn the first column is 1, the second is 2, ... * @return the {@link Field} for the specified column + * @throws SQLException if the column index is out of bounds */ - public Field getField(int sqlColumn) { + public Field getField(int sqlColumn) throws SQLException { + if (sqlColumn < 1 || sqlColumn > this.columnCount) { + throw new BigQueryJdbcException("Column index out of bounds: " + sqlColumn); + } return this.schemaFieldList.get(sqlColumn - 1); } - private boolean isPicosecondTimestampColumn(int sqlColumn) { + private boolean isPicosecondTimestampColumn(int sqlColumn) throws SQLException { if (!this.enableTimestampPicos) { return false; } @@ -83,7 +87,7 @@ public boolean isAutoIncrement(int column) { } @Override - public boolean isCaseSensitive(int column) { + public boolean isCaseSensitive(int column) throws SQLException { int colType = getColumnType(column); return colType == Types.NVARCHAR; } @@ -99,7 +103,7 @@ public boolean isCurrency(int column) { } @Override - public int isNullable(int column) { + public int isNullable(int column) throws SQLException { Mode colMode = getField(column).getMode(); if (colMode == null) { return ResultSetMetaData.columnNullableUnknown; @@ -110,7 +114,7 @@ public int isNullable(int column) { } @Override - public boolean isSigned(int column) { + public boolean isSigned(int column) throws SQLException { int colType = getColumnType(column); return colType == Types.FLOAT || colType == Types.DOUBLE @@ -119,7 +123,7 @@ public boolean isSigned(int column) { } @Override - public int getColumnDisplaySize(int column) { + public int getColumnDisplaySize(int column) throws SQLException { if (isPicosecondTimestampColumn(column)) { return 32; } @@ -142,17 +146,17 @@ public int getColumnDisplaySize(int column) { } @Override - public String getColumnLabel(int column) { + public String getColumnLabel(int column) throws SQLException { return getField(column).getName(); } @Override - public String getColumnName(int column) { + public String getColumnName(int column) throws SQLException { return getField(column).getName(); } @Override - public int getPrecision(int column) { + public int getPrecision(int column) throws SQLException { Long precision = getField(column).getPrecision(); if (precision != null) { return precision.intValue(); @@ -169,7 +173,7 @@ public int getPrecision(int column) { } @Override - public int getScale(int column) { + public int getScale(int column) throws SQLException { Long scale = getField(column).getScale(); if (scale != null) { return scale.intValue(); @@ -202,7 +206,7 @@ public String getSchemaName(int column) { return ""; // not applicable } - private StandardSQLTypeName getStandardSQLTypeName(int column) { + private StandardSQLTypeName getStandardSQLTypeName(int column) throws SQLException { Field field = getField(column); if (field.getMode() == Mode.REPEATED) { return StandardSQLTypeName.ARRAY; @@ -211,7 +215,7 @@ private StandardSQLTypeName getStandardSQLTypeName(int column) { } @Override - public int getColumnType(int column) { + public int getColumnType(int column) throws SQLException { if (isPicosecondTimestampColumn(column)) { return Types.VARCHAR; } @@ -219,7 +223,7 @@ public int getColumnType(int column) { } @Override - public String getColumnTypeName(int column) { + public String getColumnTypeName(int column) throws SQLException { if (isPicosecondTimestampColumn(column)) { return BigQueryTemporalUtility.TIMESTAMP_PICOSECONDS_TYPE_NAME; } diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadataTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadataTest.java index 322b7e52db24..b56bce432f93 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadataTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryResultSetMetadataTest.java @@ -275,6 +275,8 @@ public void testWrapperMethods() throws SQLException { resultSetMetaData.unwrap(BigQueryResultSetMetadata.class); assertThat(bqMetaData.getField(1)).isEqualTo(fieldList.get(0)); assertThat(bqMetaData.getField(8).getMode()).isEqualTo(Field.Mode.REPEATED); + assertThrows(SQLException.class, () -> bqMetaData.getField(0)); + assertThrows(SQLException.class, () -> bqMetaData.getField(fieldList.size() + 1)); BigQueryJdbcException e = assertThrows(