This is an automated email from the ASF dual-hosted git repository.

morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new ca001776658 [fix](iceberg) Fix execute action validation gaps (#61381)
ca001776658 is described below

commit ca001776658094fed49dc020c1e717f3755a6c44
Author: Socrates <[email protected]>
AuthorDate: Thu Mar 26 06:46:14 2026 +0800

    [fix](iceberg) Fix execute action validation gaps (#61381)
    
    ### What problem does this PR solve?
    
    Problem Summary:
    - Fix `rollback_to_timestamp` so epoch millis input is parsed correctly
    instead of falling through to `rollbackToTime(-1)`.
    - Reject invalid `rewrite_data_files` input when `min-file-size-bytes >
    max-file-size-bytes` during FE validation.
    - Add Iceberg regression coverage for the epoch-millis rollback path and
    invalid rewrite_data_files file-size bounds.
---
 .../action/IcebergRewriteDataFilesAction.java      |  3 ++
 .../action/IcebergRollbackToTimestampAction.java   | 21 +++++++++++++-
 .../action/test_iceberg_execute_actions.groovy     | 32 +++++++++++++++++++++-
 3 files changed, 54 insertions(+), 2 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/action/IcebergRewriteDataFilesAction.java
 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/action/IcebergRewriteDataFilesAction.java
index 885f15225b0..eb34eab0217 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/action/IcebergRewriteDataFilesAction.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/action/IcebergRewriteDataFilesAction.java
@@ -161,6 +161,9 @@ public class IcebergRewriteDataFilesAction extends 
BaseIcebergAction {
         if (this.maxFileSizeBytes == 0) {
             this.maxFileSizeBytes = (long) (targetFileSizeBytes * 1.8);
         }
+        if (this.minFileSizeBytes > this.maxFileSizeBytes) {
+            throw new UserException("min-file-size-bytes must be less than or 
equal to max-file-size-bytes");
+        }
         validateNoPartitions();
     }
 
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/action/IcebergRollbackToTimestampAction.java
 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/action/IcebergRollbackToTimestampAction.java
index be83f57ed13..f01367a85dc 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/action/IcebergRollbackToTimestampAction.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/action/IcebergRollbackToTimestampAction.java
@@ -36,6 +36,7 @@ import java.time.format.DateTimeFormatter;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.TimeZone;
 
 /**
  * Iceberg rollback to timestamp action implementation.
@@ -103,7 +104,7 @@ public class IcebergRollbackToTimestampAction extends 
BaseIcebergAction {
         Long previousSnapshotId = previousSnapshot != null ? 
previousSnapshot.snapshotId() : null;
 
         try {
-            long targetTimestamp = TimeUtils.msTimeStringToLong(timestampStr, 
TimeUtils.getTimeZone());
+            long targetTimestamp = parseTimestampMillis(timestampStr, 
TimeUtils.getTimeZone());
             
icebergTable.manageSnapshots().rollbackToTime(targetTimestamp).commit();
 
             Snapshot currentSnapshot = icebergTable.currentSnapshot();
@@ -133,4 +134,22 @@ public class IcebergRollbackToTimestampAction extends 
BaseIcebergAction {
     public String getDescription() {
         return "Rollback Iceberg table to the snapshot that was current at a 
specific timestamp";
     }
+
+    static long parseTimestampMillis(String timestampStr, TimeZone timeZone) {
+        String trimmed = timestampStr.trim();
+        try {
+            long timestampMs = Long.parseLong(trimmed);
+            if (timestampMs < 0) {
+                throw new IllegalArgumentException("Timestamp must be 
non-negative: " + timestampMs);
+            }
+            return timestampMs;
+        } catch (NumberFormatException e) {
+            long parsedTimestamp = TimeUtils.msTimeStringToLong(trimmed, 
timeZone);
+            if (parsedTimestamp < 0) {
+                throw new IllegalArgumentException("Invalid timestamp format. 
Expected ISO datetime "
+                        + "(yyyy-MM-dd HH:mm:ss.SSS) or timestamp in 
milliseconds: " + trimmed, e);
+            }
+            return parsedTimestamp;
+        }
+    }
 }
diff --git 
a/regression-test/suites/external_table_p0/iceberg/action/test_iceberg_execute_actions.groovy
 
b/regression-test/suites/external_table_p0/iceberg/action/test_iceberg_execute_actions.groovy
index b235fbbd23b..a4bcd1dd419 100644
--- 
a/regression-test/suites/external_table_p0/iceberg/action/test_iceberg_execute_actions.groovy
+++ 
b/regression-test/suites/external_table_p0/iceberg/action/test_iceberg_execute_actions.groovy
@@ -264,6 +264,23 @@ suite("test_iceberg_optimize_actions_ddl", "p0,external") {
     logger.info("Rollback timestamp result: ${rollbackTimestampResult}")
     qt_after_rollback_to_timestamp """SELECT * FROM test_rollback_timestamp 
ORDER BY id"""
 
+    String epochMillisSnapshotTime = String.valueOf(
+            dateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli())
+
+    List<List<Object>> rollbackTimestampEpochResult = sql """
+        ALTER TABLE ${catalog_name}.${db_name}.test_rollback_timestamp
+        EXECUTE rollback_to_timestamp("timestamp" = 
"${epochMillisSnapshotTime}")
+    """
+    logger.info("Rollback epoch millis result: 
${rollbackTimestampEpochResult}")
+
+    List<List<Object>> rowsAfterEpochRollback = sql """
+        SELECT id, version FROM test_rollback_timestamp ORDER BY id
+    """
+    assertTrue(rowsAfterEpochRollback.size() == 2,
+            "Expected rollback_to_timestamp with epoch millis to keep exactly 
2 rows")
+    assertTrue(rowsAfterEpochRollback[0][0] == 1 && 
rowsAfterEpochRollback[1][0] == 2,
+            "Expected rollback_to_timestamp with epoch millis to restore the 
first two snapshots")
+
 
     // 
=====================================================================================
     // Test Case 3: set_current_snapshot action
@@ -484,6 +501,19 @@ suite("test_iceberg_optimize_actions_ddl", "p0,external") {
         exception "Invalid target-file-size-bytes format: not-a-number"
     }
 
+    // Test rewrite_data_files with invalid min/max file size relationship
+    test {
+        sql """
+            ALTER TABLE ${catalog_name}.${db_name}.${table_name} EXECUTE 
rewrite_data_files
+            (
+                "target-file-size-bytes" = "536870912",
+                "min-file-size-bytes" = "1073741824",
+                "max-file-size-bytes" = "536870912"
+            )
+        """
+        exception "min-file-size-bytes must be less than or equal to 
max-file-size-bytes"
+    }
+
     // Test set_current_snapshot with both snapshot_id and ref
     test {
         sql """
@@ -631,4 +661,4 @@ test {
 }
 
   
-}
\ No newline at end of file
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to