From c6b8ce30b721fe96f8c289fc04f1f801d8b9eff8 Mon Sep 17 00:00:00 2001 From: Keshav Dandeva Date: Tue, 11 Aug 2026 14:00:00 +0000 Subject: [PATCH 1/2] feat(bigquery-jdbc): add picosecond precision support for TIMESTAMP --- .../bigquery/jdbc/BigQueryArrowResultSet.java | 22 ++++++++ .../bigquery/jdbc/BigQueryConnection.java | 6 +++ .../bigquery/jdbc/BigQueryJdbcUrlUtility.java | 8 +++ .../bigquery/jdbc/BigQueryJsonResultSet.java | 3 +- .../jdbc/BigQueryResultSetMetadata.java | 25 ++++++++- .../bigquery/jdbc/BigQueryStatement.java | 15 ++++++ .../jdbc/BigQueryTemporalUtility.java | 9 ---- .../cloud/bigquery/jdbc/DataSource.java | 17 ++++++ ...FormatTypeBigQueryCoercionUtilityTest.java | 21 ++++++++ .../jdbc/BigQueryArrowResultSetTest.java | 54 ++++++++++++++++++- .../jdbc/BigQueryJsonResultSetTest.java | 4 +- .../jdbc/BigQueryResultSetMetadataTest.java | 20 ++++++- 12 files changed, 190 insertions(+), 14 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java index 3123b6c09b40..8c82dcf7014f 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSet.java @@ -50,6 +50,7 @@ import org.apache.arrow.vector.util.ByteArrayReadableSeekableByteChannel; import org.apache.arrow.vector.util.JsonStringArrayList; import org.apache.arrow.vector.util.JsonStringHashMap; +import org.apache.arrow.vector.util.Text; /** {@link ResultSet} Implementation for Arrow datasource (Using Storage Read APIs) */ class BigQueryArrowResultSet extends BigQueryBaseResultSet { @@ -345,6 +346,27 @@ private Object getObjectInternal(int columnIndex) throws SQLException { return value; } + @Override + public String getString(int columnIndex) throws SQLException { + checkClosed(); + StandardSQLTypeName type = getStandardSQLTypeName(columnIndex); + if (type != StandardSQLTypeName.TIMESTAMP) { + return super.getString(columnIndex); + } + Object value = getObjectInternal(columnIndex); + if (value == null) { + return null; + } + if (value instanceof Text || value instanceof String) { + return BigQueryTemporalUtility.formatTimestampStringFromIso( + value.toString(), this.statement.isEnableTimestampPicos()); + } + if (value instanceof Long) { + return BigQueryTemporalUtility.formatTimestampStringFromMicroseconds((Long) value); + } + return super.getString(columnIndex); + } + @Override public Object getObject(int columnIndex) throws SQLException { diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryConnection.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryConnection.java index a4aee6142f40..36043f30f9ac 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryConnection.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryConnection.java @@ -185,6 +185,7 @@ public class BigQueryConnection extends BigQueryNoOpsConnection { int highThroughputMinTableSize; int highThroughputActivationRatio; boolean enableSession; + boolean enableTimestampPicos; boolean enableProjectDiscovery; private List discoveredProjectsCache; boolean unsupportedHTAPIFallback; @@ -372,6 +373,7 @@ public class BigQueryConnection extends BigQueryNoOpsConnection { this.sslTrustStoreProvider, this.connectionClassName); this.enableSession = ds.getEnableSession(); + this.enableTimestampPicos = ds.getEnableTimestampPicos(); this.unsupportedHTAPIFallback = ds.getUnsupportedHTAPIFallback(); this.maxResults = ds.getMaxResults(); Map queryPropertiesMap = ds.getQueryProperties(); @@ -707,6 +709,10 @@ boolean isSessionEnabled() { return this.enableSession; } + boolean isEnableTimestampPicos() { + return this.enableTimestampPicos; + } + boolean isUnsupportedHTAPIFallback() { return this.unsupportedHTAPIFallback; } diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcUrlUtility.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcUrlUtility.java index 928ed785a795..866a2b827c96 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcUrlUtility.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcUrlUtility.java @@ -185,6 +185,8 @@ protected boolean removeEldestEntry(Map.Entry> eldes static final String SSL_TRUST_STORE_TYPE_PROPERTY_NAME = "SSLTrustStoreType"; static final String SSL_TRUST_STORE_PROVIDER_PROPERTY_NAME = "SSLTrustStoreProvider"; static final int DEFAULT_REQUEST_GOOGLE_DRIVE_SCOPE_VALUE = 0; + static final String ENABLE_TIMESTAMP_PICOS_PROPERTY_NAME = "EnableTimestampPicos"; + static final boolean DEFAULT_ENABLE_TIMESTAMP_PICOS_VALUE = false; static final String MAX_BYTES_BILLED_PROPERTY_NAME = "MaximumBytesBilled"; static final Long DEFAULT_MAX_BYTES_BILLED_VALUE = 0L; static final String LABELS_PROPERTY_NAME = "Labels"; @@ -310,6 +312,12 @@ protected boolean removeEldestEntry(Map.Entry> eldes Collections.unmodifiableSet( new HashSet<>( Arrays.asList( + BigQueryConnectionProperty.newBuilder() + .setName(ENABLE_TIMESTAMP_PICOS_PROPERTY_NAME) + .setDescription( + "Enable 12-digit picosecond precision for TIMESTAMP columns. Set to 1 to enable. Disabled (0) by default.") + .setDefaultValue(String.valueOf(DEFAULT_ENABLE_TIMESTAMP_PICOS_VALUE)) + .build(), BigQueryConnectionProperty.newBuilder() .setName(MAX_BYTES_BILLED_PROPERTY_NAME) .setDescription( diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSet.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSet.java index 8a18ecb46a6e..8615ed7ebade 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSet.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSet.java @@ -229,7 +229,8 @@ public String getString(int columnIndex) throws SQLException { if (value.getAttribute() == Attribute.REPEATED || value.getAttribute() == Attribute.RECORD) { return super.getString(columnIndex); } - return BigQueryTemporalUtility.formatTimestampString(value.getStringValue()); + return BigQueryTemporalUtility.formatTimestampString( + value.getStringValue(), this.statement.isEnableTimestampPicos()); } @Override 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 c3fd8151d52c..2ecb8d998ce4 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 @@ -51,6 +51,16 @@ Statement getStatement() { return this.statement; } + private boolean isTimestampPicosEnabled() { + return this.statement instanceof BigQueryStatement + && ((BigQueryStatement) this.statement).isEnableTimestampPicos(); + } + + private boolean supportsPicoseconds(int sqlColumn) { + Long timestampPrecision = getField(sqlColumn).getTimestampPrecision(); + return timestampPrecision != null && timestampPrecision > 6; + } + private Field getField(int sqlColumn) { return this.schemaFieldList.get(sqlColumn - 1); } @@ -116,7 +126,10 @@ public int getColumnDisplaySize(int column) { case Types.NUMERIC: return 14; case Types.TIMESTAMP: - return 16; + if (isTimestampPicosEnabled() && supportsPicoseconds(column)) { + return 32; + } + return 26; default: return DEFAULT_DISPLAY_SIZE; } @@ -139,6 +152,11 @@ public int getPrecision(int column) { return precision.intValue(); } StandardSQLTypeName type = getStandardSQLTypeName(column); + if (type == StandardSQLTypeName.TIMESTAMP + && isTimestampPicosEnabled() + && supportsPicoseconds(column)) { + return 32; + } BigQueryJdbcTypeMappings.ColumnTypeInfo typeInfo = BigQueryJdbcTypeMappings.STANDARD_TYPE_INFO.get(type); if (typeInfo != null && typeInfo.columnSize != null) { @@ -154,6 +172,11 @@ public int getScale(int column) { return scale.intValue(); } StandardSQLTypeName type = getStandardSQLTypeName(column); + if (type == StandardSQLTypeName.TIMESTAMP + && isTimestampPicosEnabled() + && supportsPicoseconds(column)) { + return 12; + } BigQueryJdbcTypeMappings.ColumnTypeInfo typeInfo = BigQueryJdbcTypeMappings.STANDARD_TYPE_INFO.get(type); if (typeInfo != null && typeInfo.decimalDigits != null) { diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryStatement.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryStatement.java index 30c11d13d263..fb45927c17e8 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryStatement.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryStatement.java @@ -49,12 +49,14 @@ import com.google.cloud.bigquery.exception.BigQueryJdbcSqlSyntaxErrorException; import com.google.cloud.bigquery.storage.v1.ArrowRecordBatch; import com.google.cloud.bigquery.storage.v1.ArrowSchema; +import com.google.cloud.bigquery.storage.v1.ArrowSerializationOptions; import com.google.cloud.bigquery.storage.v1.BigQueryReadClient; import com.google.cloud.bigquery.storage.v1.CreateReadSessionRequest; import com.google.cloud.bigquery.storage.v1.DataFormat; import com.google.cloud.bigquery.storage.v1.ReadRowsRequest; import com.google.cloud.bigquery.storage.v1.ReadRowsResponse; import com.google.cloud.bigquery.storage.v1.ReadSession; +import com.google.cloud.bigquery.storage.v1.ReadSession.TableReadOptions; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.Uninterruptibles; @@ -846,6 +848,15 @@ ResultSet processArrowResultSet(TableResult results, Job job) throws SQLExceptio ReadSession.Builder sessionBuilder = ReadSession.newBuilder().setTable(srcTable).setDataFormat(DataFormat.ARROW); + if (this.connection.isEnableTimestampPicos()) { + TableReadOptions.Builder tableReadOptionsBuilder = TableReadOptions.newBuilder(); + tableReadOptionsBuilder + .getArrowSerializationOptionsBuilder() + .setPicosTimestampPrecision( + ArrowSerializationOptions.PicosTimestampPrecision.TIMESTAMP_PRECISION_PICOS); + sessionBuilder.setReadOptions(tableReadOptionsBuilder.build()); + } + CreateReadSessionRequest.Builder builder = CreateReadSessionRequest.newBuilder() .setParent(parent) @@ -1671,6 +1682,10 @@ public Connection getConnection() { return this.connection; } + boolean isEnableTimestampPicos() { + return this.connection.isEnableTimestampPicos(); + } + public boolean hasMoreResults() { if (this.parentJobId == null) { return false; 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 b2baa550858c..b7c4e127e042 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 @@ -164,15 +164,6 @@ public static Instant parseEpochDecimalToInstant(String epochDecimal) { return Instant.ofEpochSecond(seconds, nanos); } - /** - * Formats a numeric epoch decimal string into standard SQL timestamp string format ("yyyy-MM-dd - * HH:mm:ss.ffffff"). Sub-microsecond precision is deterministically truncated (down) to prevent - * timestamp boundary rollovers. - */ - public static String formatTimestampString(String epochDecimal) { - return formatTimestampString(epochDecimal, false); - } - /** * Formats a numeric epoch decimal string into standard SQL timestamp string format ("yyyy-MM-dd * HH:mm:ss.ffffff[ffffff]"). Sub-microsecond / sub-picosecond precision is deterministically diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/DataSource.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/DataSource.java index 2c07fc483bac..c120916f3f8b 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/DataSource.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/DataSource.java @@ -58,6 +58,7 @@ public class DataSource implements javax.sql.DataSource { private Map queryProperties; private String logLevel; private Boolean enableSession; + private Boolean enableTimestampPicos; private String logPath; private String gcpTelemetryProjectId; private String gcpTelemetryCredentials; @@ -149,6 +150,12 @@ public class DataSource implements javax.sql.DataSource { .put( BigQueryJdbcUrlUtility.GCP_TELEMETRY_CREDENTIALS_PROPERTY_NAME, DataSource::setGcpTelemetryCredentials) + .put( + BigQueryJdbcUrlUtility.ENABLE_TIMESTAMP_PICOS_PROPERTY_NAME, + (ds, val) -> + ds.setEnableTimestampPicos( + BigQueryJdbcUrlUtility.convertIntToBoolean( + val, BigQueryJdbcUrlUtility.ENABLE_TIMESTAMP_PICOS_PROPERTY_NAME))) .put( BigQueryJdbcUrlUtility.ENABLE_HTAPI_PROPERTY_NAME, (ds, val) -> @@ -925,6 +932,16 @@ public Boolean getUnsupportedHTAPIFallback() { : BigQueryJdbcUrlUtility.DEFAULT_UNSUPPORTED_HTAPI_FALLBACK_VALUE; } + public Boolean getEnableTimestampPicos() { + return enableTimestampPicos != null + ? enableTimestampPicos + : BigQueryJdbcUrlUtility.DEFAULT_ENABLE_TIMESTAMP_PICOS_VALUE; + } + + public void setEnableTimestampPicos(Boolean enableTimestampPicos) { + this.enableTimestampPicos = enableTimestampPicos; + } + public Boolean getEnableSession() { return enableSession != null ? enableSession 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 index 02f5e77c738a..5e7521294d7b 100644 --- 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 @@ -29,6 +29,7 @@ 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.Period; @@ -158,6 +159,26 @@ public void longToTimestamp() { .isEqualTo(new Timestamp(1408452095220L)); } + @Test + public void textToTimestamp() { + Text textUtc = new Text("2026-04-08 10:00:00.123456789123 UTC"); + Timestamp expected = Timestamp.from(Instant.parse("2026-04-08T10:00:00.123456789Z")); + assertThat(INSTANCE.coerceTo(Timestamp.class, textUtc)).isEqualTo(expected); + + Text textIso = new Text("2026-04-08T10:00:00.123456789123Z"); + assertThat(INSTANCE.coerceTo(Timestamp.class, textIso)).isEqualTo(expected); + } + + @Test + public void stringToTimestamp() { + String strUtc = "2026-04-08 10:00:00.123456789123 UTC"; + Timestamp expected = Timestamp.from(Instant.parse("2026-04-08T10:00:00.123456789Z")); + assertThat(INSTANCE.coerceTo(Timestamp.class, strUtc)).isEqualTo(expected); + + String strIso = "2026-04-08T10:00:00.123456789123Z"; + assertThat(INSTANCE.coerceTo(Timestamp.class, strIso)).isEqualTo(expected); + } + @Test public void nullToTime() { assertThat(INSTANCE.coerceTo(Time.class, null)).isNull(); diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSetTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSetTest.java index 1ffd05ff4cd6..af71a4d45f91 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSetTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryArrowResultSetTest.java @@ -23,6 +23,7 @@ import static org.apache.arrow.vector.types.Types.MinorType.VARCHAR; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.google.cloud.bigquery.Field; import com.google.cloud.bigquery.Field.Mode; @@ -437,5 +438,56 @@ private int resultSetRowCount(BigQueryArrowResultSet resultSet) throws SQLExcept return rowCount; } - // TODO: Unit Test for iteration and getters + @Test + public void testPicosecondTimestampArrowVector() throws Exception { + RootAllocator allocator = new RootAllocator(); + VarCharVector timeStampPicosVector = new VarCharVector("timeStampField", allocator); + timeStampPicosVector.allocateNew(1); + timeStampPicosVector.set(0, new Text("2026-04-08T10:00:00.123456789123Z")); + timeStampPicosVector.setValueCount(1); + + VectorSchemaRoot picosRoot = new VectorSchemaRoot(ImmutableList.of(timeStampPicosVector)); + ArrowSchema arrowSchema = + ArrowSchema.newBuilder() + .setSerializedSchema(serializeSchema(picosRoot.getSchema())) + .build(); + ArrowRecordBatch recordBatch = + ArrowRecordBatch.newBuilder() + .setSerializedRecordBatch(serializeVectorSchemaRoot(picosRoot)) + .build(); + + BigQueryArrowBatchWrapper batchWrapper = BigQueryArrowBatchWrapper.of(recordBatch, false); + BlockingQueue picosBuffer = new LinkedBlockingDeque<>(2); + picosBuffer.add(batchWrapper); + picosBuffer.add(BigQueryArrowBatchWrapper.of(null, true)); + + Schema bqSchema = + Schema.of(FieldList.of(Field.of("timeStampField", StandardSQLTypeName.TIMESTAMP))); + BigQueryStatement mockStatement = mock(BigQueryStatement.class); + when(mockStatement.isEnableTimestampPicos()).thenReturn(true); + + BigQueryArrowResultSet rs = + BigQueryArrowResultSet.of( + bqSchema, arrowSchema, 1, mockStatement, picosBuffer, mock(Future.class), null); + + assertThat(rs.next()).isTrue(); + // getString returns full 12-digit picosecond string + assertThat(rs.getString("timeStampField")).isEqualTo("2026-04-08 10:00:00.123456789123"); + assertThat(rs.getString(1)).isEqualTo("2026-04-08 10:00:00.123456789123"); + + // getTimestamp returns java.sql.Timestamp with 9 digits of nanoseconds + Timestamp ts = rs.getTimestamp("timeStampField"); + assertThat(ts).isNotNull(); + assertThat(ts.getNanos()).isEqualTo(123456789); + + // getObject returns java.sql.Timestamp + Object obj = rs.getObject(1); + assertThat(obj).isInstanceOf(Timestamp.class); + assertThat(((Timestamp) obj).getNanos()).isEqualTo(123456789); + + rs.close(); + timeStampPicosVector.close(); + picosRoot.close(); + allocator.close(); + } } diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSetTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSetTest.java index d95b39f065ce..eba951341c10 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSetTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/BigQueryJsonResultSetTest.java @@ -20,6 +20,7 @@ import static java.time.Month.MARCH; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.google.cloud.bigquery.Field; import com.google.cloud.bigquery.FieldList; @@ -509,7 +510,8 @@ public void testGetObjectWithType_failure(Object column, Class type) throws S } @Test - public void testGetString_timestamp() throws SQLException { + public void testGetString_timestampWithPicoseconds() throws SQLException { + when(statement.isEnableTimestampPicos()).thenReturn(true); assertThat(resetResultSet()).isTrue(); bigQueryJsonResultSet.next(); assertThat(bigQueryJsonResultSet.getString("fifth")).isEqualTo("2023-03-30 11:14:19.820000"); 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 8261a14dc981..7aef76229b33 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 @@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.params.provider.Arguments.arguments; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.google.cloud.bigquery.Field; import com.google.cloud.bigquery.FieldList; @@ -196,7 +197,7 @@ public void testColumnDisplaySize() throws SQLException { assertThat(resultSetMetaData.getColumnDisplaySize(2)).isEqualTo(10); assertThat(resultSetMetaData.getColumnDisplaySize(3)).isEqualTo(14); assertThat(resultSetMetaData.getColumnDisplaySize(12)).isEqualTo(50); - assertThat(resultSetMetaData.getColumnDisplaySize(5)).isEqualTo(16); + assertThat(resultSetMetaData.getColumnDisplaySize(5)).isEqualTo(26); } // Nested Types @@ -295,4 +296,21 @@ public void testIsSearchableForAllTypes(StandardSQLTypeName type) throws SQLExce ResultSetMetaData metaData = resultSet.getMetaData(); assertThat(metaData.isSearchable(1)).isTrue(); } + + @Test + public void testTimestampPicosecondsMetadata() throws SQLException { + Field picosTimestampField = + Field.newBuilder("picosTs", StandardSQLTypeName.TIMESTAMP) + .setTimestampPrecision(12L) + .build(); + Schema schema = Schema.of(FieldList.of(picosTimestampField)); + when(statement.isEnableTimestampPicos()).thenReturn(true); + BigQueryJsonResultSet jsonRs = + BigQueryJsonResultSet.of(schema, 1L, null, statement, (Future[]) null); + ResultSetMetaData metadata = jsonRs.getMetaData(); + + assertThat(metadata.getColumnDisplaySize(1)).isEqualTo(32); + assertThat(metadata.getPrecision(1)).isEqualTo(32); + assertThat(metadata.getScale(1)).isEqualTo(12); + } } From 32114cbfd4986d97725349b9ae295113d108a038 Mon Sep 17 00:00:00 2001 From: Keshav Dandeva Date: Tue, 11 Aug 2026 14:23:12 +0000 Subject: [PATCH 2/2] update property description --- .../com/google/cloud/bigquery/jdbc/BigQueryJdbcUrlUtility.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcUrlUtility.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcUrlUtility.java index 866a2b827c96..ae8721fca927 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcUrlUtility.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryJdbcUrlUtility.java @@ -315,7 +315,7 @@ protected boolean removeEldestEntry(Map.Entry> eldes BigQueryConnectionProperty.newBuilder() .setName(ENABLE_TIMESTAMP_PICOS_PROPERTY_NAME) .setDescription( - "Enable 12-digit picosecond precision for TIMESTAMP columns. Set to 1 to enable. Disabled (0) by default.") + "Enables or disables 12-digit picosecond precision for TIMESTAMP columns. Disabled by default.") .setDefaultValue(String.valueOf(DEFAULT_ENABLE_TIMESTAMP_PICOS_VALUE)) .build(), BigQueryConnectionProperty.newBuilder()