diff --git a/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/FieldValueTest.java b/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/FieldValueTest.java index 958e20659868..e6b08ec6b8a3 100644 --- a/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/FieldValueTest.java +++ b/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/FieldValueTest.java @@ -151,6 +151,18 @@ public void testInt64Timestamp() { assertEquals(expected, received); } + @Test + public void testLosslessMaxTimestamp() { + // Test the lossless behavior (useInt64Timestamps = true) + // The backend returns a 64-bit integer string for microseconds when this option is enabled + String losslessTimestampString = "253402300799999999"; + FieldValue losslessValue = + FieldValue.of(FieldValue.Attribute.PRIMITIVE, losslessTimestampString, true); + + // Exactly matches the microsecond equivalent of 9999-12-31 23:59:59.999999 + assertEquals(253402300799999999L, losslessValue.getTimestampValue()); + } + @Test public void testEquals() { FieldValue booleanValue = FieldValue.of(FieldValue.Attribute.PRIMITIVE, "false"); diff --git a/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java b/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java index b7bfe586f022..3185354364d2 100644 --- a/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java +++ b/java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/it/ITBigQueryTest.java @@ -1215,6 +1215,56 @@ static GoogleCredentials loadCredentials(String credentialFile) { } } + @Test + void testLosslessMaxTimestampIntegration() throws InterruptedException { + String query = "SELECT TIMESTAMP '9999-12-31 23:59:59.999999 UTC' as max_ts"; + QueryJobConfiguration config = QueryJobConfiguration.newBuilder(query).build(); + + // 1. Test lossless 64-bit integer parsing (useInt64Timestamps = true) + DataFormatOptions losslessOptions = + DataFormatOptions.newBuilder().useInt64Timestamp(true).build(); + BigQuery losslessBigQuery = + bigquery.getOptions().toBuilder() + .setDataFormatOptions(losslessOptions) + .build() + .getService(); + + TableResult losslessResult = losslessBigQuery.query(config); + assertEquals(1L, losslessResult.getTotalRows()); + for (FieldValueList row : losslessResult.iterateAll()) { + long exactMicros = row.get("max_ts").getTimestampValue(); + assertEquals(253402300799999999L, exactMicros); + } + + // 2. Test lossy FLOAT64 rounding behavior (useInt64Timestamps = false) + DataFormatOptions floatOptions = + DataFormatOptions.newBuilder().useInt64Timestamp(false).build(); + BigQuery floatBigQuery = + bigquery.getOptions().toBuilder().setDataFormatOptions(floatOptions).build().getService(); + + TableResult floatResult = floatBigQuery.query(config); + assertEquals(1L, floatResult.getTotalRows()); + for (FieldValueList row : floatResult.iterateAll()) { + long roundedMicros = row.get("max_ts").getTimestampValue(); + assertEquals(253402300800000000L, roundedMicros); + } + + // 3. Test ISO8601 timestamp formatting + DataFormatOptions isoOptions = + DataFormatOptions.newBuilder() + .timestampFormatOptions(DataFormatOptions.TimestampFormatOptions.ISO8601_STRING) + .build(); + BigQuery isoBigQuery = + bigquery.getOptions().toBuilder().setDataFormatOptions(isoOptions).build().getService(); + + TableResult isoResult = isoBigQuery.query(config); + assertEquals(1L, isoResult.getTotalRows()); + for (FieldValueList row : isoResult.iterateAll()) { + String isoValue = row.get("max_ts").getStringValue(); + assertEquals("9999-12-31T23:59:59.999999Z", isoValue); + } + } + @Test void testListDatasets() { Page datasets = bigquery.listDatasets("bigquery-public-data");