gemini-code-assist[bot] commented on code in PR #39344:
URL: https://github.com/apache/beam/pull/39344#discussion_r3589688889


##########
sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/IcebergUtils.java:
##########
@@ -613,21 +639,28 @@ private static Object getLogicalTypeValue(Object 
icebergValue, Schema.FieldType
         return LocalTime.parse(strValue);
       } else if (type.isLogicalType(SqlTypes.DATETIME.getIdentifier())) {
         return LocalDateTime.parse(strValue);
+      } else if (type.isLogicalType(Timestamp.IDENTIFIER)) {
+        return java.time.Instant.parse(strValue);
       }

Review Comment:
   ![high](https://www.gstatic.com/codereviewagent/high-priority.svg)
   
   Using `java.time.Instant.parse(strValue)` will throw a 
`DateTimeParseException` if the string contains a timezone offset other than 
`Z` (for example, `-08:00` or `+03:27`), because `Instant.parse` strictly 
requires a UTC instant representation ending in `Z`.
   
   To robustly parse any ISO-8601 string with a timezone offset, use 
`OffsetDateTime.parse(strValue).toInstant()` instead.
   
   ```suggestion
         } else if (type.isLogicalType(Timestamp.IDENTIFIER)) {
           return OffsetDateTime.parse(strValue).toInstant();
         }
   ```



##########
sdks/java/io/iceberg/src/test/java/org/apache/beam/sdk/io/iceberg/IcebergIOReadTest.java:
##########
@@ -731,6 +737,48 @@ public void testBatchReadBetweenTimestamps() throws 
IOException {
     runReadWithBoundary(false, false);
   }
 
+  @Test
+  public void testTimestampUpdateCompat() throws IOException {
+    String val = "2026-07-15T13:18:20.053123+03:27";
+    OffsetDateTime ts = OffsetDateTime.parse(val);
+
+    TableIdentifier tableId =
+        TableIdentifier.of("default", "table" + 
Long.toString(UUID.randomUUID().hashCode(), 16));
+    org.apache.iceberg.Schema schema =
+        new org.apache.iceberg.Schema(
+            Collections.singletonList(required(1, "ts", 
Types.TimestampType.withZone())),
+            ImmutableSet.of(1));
+    Table table = warehouse.createTable(tableId, schema);
+    DataFile file =
+        warehouse.writeData(
+            "date.parquet", schema, 
Collections.singletonList(ImmutableMap.of("ts", ts)));
+    table.newFastAppend().appendFile(file).commit();
+
+    IcebergIO.ReadRows read = 
IcebergIO.readRows(catalogConfig()).from(tableId);
+    if (useIncrementalScan) {
+      read = read.withCdc().toSnapshot(table.currentSnapshot().snapshotId());
+    }
+
+    Schema expectedBeamSchema =
+        Schema.builder().addLogicalTypeField("ts", Timestamp.MICROS).build();
+    Row expectedRow = 
Row.withSchema(expectedBeamSchema).addValue(ts.toInstant()).build();
+
+    PCollection<Row> output = testPipeline.apply(read).apply(new PrintRow());
+    PAssert.that(output).containsInAnyOrder(expectedRow);
+    testPipeline.run().waitUntilFinish();
+
+    // test again but with older versions that require primitive DATETIME type
+    Schema expectedLegacyBeamSchema = 
Schema.builder().addDateTimeField("ts").build();
+    Row expectedLegacyRow =
+        
Row.withSchema(expectedLegacyBeamSchema).addValue(DateTime.parse(val)).build();
+
+    Pipeline testPipeline2 = Pipeline.create();
+    
testPipeline2.getOptions().as(StreamingOptions.class).setUpdateCompatibilityVersion("2.75.0");
+    PCollection<Row> outputLegacy = testPipeline2.apply(read).apply(new 
PrintRow());
+    PAssert.that(outputLegacy).containsInAnyOrder(expectedLegacyRow);
+    testPipeline2.run().waitUntilFinish();
+  }

Review Comment:
   ![medium](https://www.gstatic.com/codereviewagent/medium-priority.svg)
   
   Using `Pipeline.create()` with no arguments in tests is an anti-pattern 
because it does not inherit the pipeline options (such as the runner or 
classpath configurations) set up for the test suite.
   
   Additionally, running two pipelines sequentially within a single `@Test` 
method prevents the use of the `@Rule` `TestPipeline` for both runs.
   
   It is highly recommended to split this test into two separate test methods 
(e.g., `testTimestampMicros` and `testTimestampLegacyCompat`). This allows both 
tests to use the `TestPipeline` rule properly and ensures they run with the 
correct test runner and options.
   
   ```java
     private Table createTableWithTimestamp(TableIdentifier tableId, 
OffsetDateTime ts) throws IOException {
       org.apache.iceberg.Schema schema =
           new org.apache.iceberg.Schema(
               Collections.singletonList(required(1, "ts", 
Types.TimestampType.withZone())),
               ImmutableSet.of(1));
       Table table = warehouse.createTable(tableId, schema);
       DataFile file =
           warehouse.writeData(
               "date.parquet", schema, 
Collections.singletonList(ImmutableMap.of("ts", ts)));
       table.newFastAppend().appendFile(file).commit();
       return table;
     }
   
     @Test
     public void testTimestampMicros() throws IOException {
       String val = "2026-07-15T13:18:20.053123+03:27";
       OffsetDateTime ts = OffsetDateTime.parse(val);
       TableIdentifier tableId =
           TableIdentifier.of("default", "table" + 
Long.toString(UUID.randomUUID().hashCode(), 16));
       Table table = createTableWithTimestamp(tableId, ts);
   
       IcebergIO.ReadRows read = 
IcebergIO.readRows(catalogConfig()).from(tableId);
       if (useIncrementalScan) {
         read = read.withCdc().toSnapshot(table.currentSnapshot().snapshotId());
       }
   
       Schema expectedBeamSchema =
           Schema.builder().addLogicalTypeField("ts", Timestamp.MICROS).build();
       Row expectedRow = 
Row.withSchema(expectedBeamSchema).addValue(ts.toInstant()).build();
   
       PCollection<Row> output = testPipeline.apply(read).apply(new PrintRow());
       PAssert.that(output).containsInAnyOrder(expectedRow);
       testPipeline.run().waitUntilFinish();
     }
   
     @Test
     public void testTimestampLegacyCompat() throws IOException {
       String val = "2026-07-15T13:18:20.053123+03:27";
       OffsetDateTime ts = OffsetDateTime.parse(val);
       TableIdentifier tableId =
           TableIdentifier.of("default", "table" + 
Long.toString(UUID.randomUUID().hashCode(), 16));
       Table table = createTableWithTimestamp(tableId, ts);
   
       IcebergIO.ReadRows read = 
IcebergIO.readRows(catalogConfig()).from(tableId);
       if (useIncrementalScan) {
         read = read.withCdc().toSnapshot(table.currentSnapshot().snapshotId());
       }
   
       Schema expectedLegacyBeamSchema = 
Schema.builder().addDateTimeField("ts").build();
       Row expectedLegacyRow =
           
Row.withSchema(expectedLegacyBeamSchema).addValue(DateTime.parse(val)).build();
   
       
testPipeline.getOptions().as(StreamingOptions.class).setUpdateCompatibilityVersion("2.75.0");
       PCollection<Row> outputLegacy = testPipeline.apply(read).apply(new 
PrintRow());
       PAssert.that(outputLegacy).containsInAnyOrder(expectedLegacyRow);
       testPipeline.run().waitUntilFinish();
     }
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to