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

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


The following commit(s) were added to refs/heads/master by this push:
     new b53ea1b1c4d [FLINK-40481][table] Fix SHOW CREATE FROM_NOW timestamp 
direction (#29030)
b53ea1b1c4d is described below

commit b53ea1b1c4d5ab0ec882559a4a050d82ae5740e0
Author: Arvid Heise <[email protected]>
AuthorDate: Fri Aug 28 17:18:24 2026 +0200

    [FLINK-40481][table] Fix SHOW CREATE FROM_NOW timestamp direction (#29030)
    
    ShowCreateUtil.extractStartMode adds the interval to now() when
    annotating FROM_NOW/RESUME_OR_FROM_NOW in SHOW CREATE output,
    producing a future timestamp in the "Evaluated to
    FROM_TIMESTAMP(...)" comment instead of the documented past
    resolution. Purely a display bug; it does not affect which data is
    actually read.
    
    Thread a Clock through extractStartMode and through a package-private
    overload of buildShowCreateMaterializedTableRow (the public API keeps
    its existing signatures and hardcodes Clock.systemUTC()), switch to
    now(clock).minus(amount), and normalize the clock to UTC before
    reading its wall-clock time so a clock in a non-UTC zone can never be
    silently mislabeled by the subsequent toInstant(ZoneOffset.UTC).
    
    ShowCreateUtilTest previously normalized the evaluated timestamp in
    both the actual and expected strings via regex before comparing,
    since it relied on the wall clock and the exact value couldn't be
    known in advance. That normalization erased the value being tested,
    so it could never have caught a sign error. Inject FIXED_CLOCK into
    the parameterized materialized-table test instead and assert the
    literal expected string with no normalization; that case already
    covers the sign fix, so the only additional unit test kept is one
    proving a non-UTC clock zone can't leak into the display.
    
    Co-authored-by: Arvid Heise <[email protected]>
---
 .../flink/table/api/internal/ShowCreateUtil.java   | 65 +++++++++++++++++++++-
 .../table/api/internal/ShowCreateUtilTest.java     | 63 ++++++++++++---------
 2 files changed, 100 insertions(+), 28 deletions(-)

diff --git 
a/flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/api/internal/ShowCreateUtil.java
 
b/flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/api/internal/ShowCreateUtil.java
index 9ea15b1eca8..0e690c4d0cb 100644
--- 
a/flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/api/internal/ShowCreateUtil.java
+++ 
b/flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/api/internal/ShowCreateUtil.java
@@ -43,6 +43,7 @@ import org.apache.flink.table.utils.EncodingUtils;
 
 import org.apache.commons.lang3.StringUtils;
 
+import java.time.Clock;
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
@@ -149,6 +150,32 @@ public class ShowCreateUtil {
                 additionalSensitiveKeys);
     }
 
+    /**
+     * Package-private overload of the convenience {@code 
buildShowCreateMaterializedTableRow}
+     * accepting a {@link Clock}, for the same reason as above.
+     */
+    static String buildShowCreateMaterializedTableRow(
+            ResolvedCatalogMaterializedTable table,
+            ObjectIdentifier tableIdentifier,
+            boolean isTemporary,
+            boolean createOrAlter,
+            ZoneId timeZoneId,
+            SqlFactory sqlFactory,
+            List<String> additionalSensitiveKeys,
+            Clock clock) {
+        return buildShowCreateMaterializedTableRow(
+                table,
+                tableIdentifier,
+                isTemporary,
+                createOrAlter,
+                timeZoneId,
+                sqlFactory,
+                true,
+                true,
+                additionalSensitiveKeys,
+                clock);
+    }
+
     /** Show create materialized table statement only for materialized tables. 
*/
     public static String buildShowCreateMaterializedTableRow(
             ResolvedCatalogMaterializedTable table,
@@ -160,6 +187,36 @@ public class ShowCreateUtil {
             boolean includeFreshness,
             boolean includeRefreshMode,
             List<String> additionalSensitiveKeys) {
+        return buildShowCreateMaterializedTableRow(
+                table,
+                tableIdentifier,
+                isTemporary,
+                createOrAlter,
+                timeZoneId,
+                sqlFactory,
+                includeFreshness,
+                includeRefreshMode,
+                additionalSensitiveKeys,
+                Clock.systemUTC());
+    }
+
+    /**
+     * Show create materialized table statement only for materialized tables.
+     *
+     * <p>Package-private overload accepting a {@link Clock} so tests can pin 
the "Evaluated to
+     * FROM_TIMESTAMP(...)" comment for FROM_NOW/RESUME_OR_FROM_NOW to a 
deterministic value.
+     */
+    static String buildShowCreateMaterializedTableRow(
+            ResolvedCatalogMaterializedTable table,
+            ObjectIdentifier tableIdentifier,
+            boolean isTemporary,
+            boolean createOrAlter,
+            ZoneId timeZoneId,
+            SqlFactory sqlFactory,
+            boolean includeFreshness,
+            boolean includeRefreshMode,
+            List<String> additionalSensitiveKeys,
+            Clock clock) {
         validateTableKind(table, tableIdentifier, 
TableKind.MATERIALIZED_TABLE);
         StringBuilder sb =
                 new StringBuilder()
@@ -184,7 +241,7 @@ public class ShowCreateUtil {
                 .ifPresent(partitionedBy -> 
sb.append(formatPartitionedBy(partitionedBy)));
         extractFormattedOptions(table.getOptions(), PRINT_INDENT, 
additionalSensitiveKeys)
                 .ifPresent(v -> sb.append("WITH 
(\n").append(v).append("\n)\n"));
-        sb.append(extractStartMode(table, timeZoneId)).append("\n");
+        sb.append(extractStartMode(table, timeZoneId, clock)).append("\n");
         if (includeFreshness) {
             sb.append(extractFreshness(table)).append("\n");
         }
@@ -386,7 +443,7 @@ public class ShowCreateUtil {
     }
 
     static String extractStartMode(
-            ResolvedCatalogMaterializedTable materializedTable, ZoneId 
timeZoneId) {
+            ResolvedCatalogMaterializedTable materializedTable, ZoneId 
timeZoneId, Clock clock) {
         StringBuilder sb = new StringBuilder("START_MODE = ");
         StartMode startMode = materializedTable.getStartMode().get();
         switch (startMode.getKind()) {
@@ -414,7 +471,9 @@ public class ShowCreateUtil {
                         .append(" /* Evaluated to FROM_TIMESTAMP(TIMESTAMP '")
                         .append(
                                 getFormattedLocalDateTime(
-                                        
LocalDateTime.now().plus(amount).toInstant(ZoneOffset.UTC),
+                                        
LocalDateTime.now(clock.withZone(ZoneOffset.UTC))
+                                                .minus(amount)
+                                                .toInstant(ZoneOffset.UTC),
                                         ZoneOffset.UTC))
                         .append("') at execution */");
                 break;
diff --git 
a/flink-table/flink-table-api-java/src/test/java/org/apache/flink/table/api/internal/ShowCreateUtilTest.java
 
b/flink-table/flink-table-api-java/src/test/java/org/apache/flink/table/api/internal/ShowCreateUtilTest.java
index 750818181d4..9a4391973c8 100644
--- 
a/flink-table/flink-table-api-java/src/test/java/org/apache/flink/table/api/internal/ShowCreateUtilTest.java
+++ 
b/flink-table/flink-table-api-java/src/test/java/org/apache/flink/table/api/internal/ShowCreateUtilTest.java
@@ -51,15 +51,15 @@ import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.CsvSource;
 import org.junit.jupiter.params.provider.MethodSource;
 
+import java.time.Clock;
 import java.time.Instant;
-import java.time.Period;
+import java.time.ZoneId;
 import java.time.ZoneOffset;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.regex.Pattern;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -72,9 +72,13 @@ class ShowCreateUtilTest {
     private static final ObjectIdentifier MATERIALIZED_TABLE_IDENTIFIER =
             ObjectIdentifier.of("catalogName", "dbName", 
"materializedTableName");
 
-    private static final Pattern START_MODE_EVALUATED_TIMESTAMP =
-            Pattern.compile(
-                    "/\\* Evaluated to FROM_TIMESTAMP\\(TIMESTAMP '[^']*'\\) 
at execution \\*/");
+    /**
+     * Fixed clock used so the "Evaluated to FROM_TIMESTAMP(...)" comment for
+     * FROM_NOW/RESUME_OR_FROM_NOW resolves to a deterministic, assertable 
value instead of the wall
+     * clock.
+     */
+    private static final Clock FIXED_CLOCK =
+            Clock.fixed(Instant.parse("2020-12-12T23:18:12Z"), ZoneOffset.UTC);
 
     private static final ResolvedSchema ONE_COLUMN_SCHEMA =
             ResolvedSchema.of(Column.physical("id", DataTypes.INT()));
@@ -142,12 +146,9 @@ class ShowCreateUtilTest {
                         createOrAlter,
                         ZoneOffset.UTC,
                         DefaultSqlFactory.INSTANCE,
-                        List.of());
-        final String fixedTimestamp = "1970-01-02 12:34:56";
-        final String normalizedMTString =
-                setFixedTimestamp(createMaterializedTableString, 
fixedTimestamp);
-        final String normalizedExpected = setFixedTimestamp(expected, 
fixedTimestamp);
-        assertThat(normalizedMTString).isEqualTo(normalizedExpected);
+                        List.of(),
+                        FIXED_CLOCK);
+        assertThat(createMaterializedTableString).isEqualTo(expected);
     }
 
     @ParameterizedTest(name = "includeFreshness={0}, includeRefreshMode={1}")
@@ -195,6 +196,29 @@ class ShowCreateUtilTest {
         assertThat(result).isEqualTo(expected.toString());
     }
 
+    @Test
+    void extractStartModeFromNowIsUnaffectedByClockZone() {
+        final Clock fixedClock =
+                Clock.fixed(Instant.parse("2020-12-12T23:18:12Z"), 
ZoneId.of("America/New_York"));
+        final ResolvedCatalogMaterializedTable materializedTable =
+                createResolvedMaterialized(
+                        ONE_COLUMN_SCHEMA,
+                        null,
+                        List.of(),
+                        null,
+                        StartMode.of(StartModeKind.FROM_NOW, Interval.of(3, 
TimeUnit.MINUTE)),
+                        IntervalFreshness.ofMinute(1),
+                        RefreshMode.CONTINUOUS,
+                        "SELECT 1",
+                        "SELECT 1");
+
+        assertThat(ShowCreateUtil.extractStartMode(materializedTable, 
ZoneOffset.UTC, fixedClock))
+                .isEqualTo(
+                        "START_MODE = FROM_NOW(INTERVAL '3' MINUTE) /* 
Evaluated to"
+                                + " FROM_TIMESTAMP(TIMESTAMP '2020-12-12 
23:15:12') at execution"
+                                + " */");
+    }
+
     @ParameterizedTest(name = "{index}: {1}")
     @MethodSource("argsForShowCreateCatalog")
     void showCreateCatalog(CatalogDescriptor catalogDescriptor, String 
expected) {
@@ -407,7 +431,7 @@ class ShowCreateUtilTest {
                 "%sMATERIALIZED TABLE 
`catalogName`.`dbName`.`materializedTableName` (\n"
                         + "  `id` INT\n"
                         + ")\n"
-                        + "START_MODE = FROM_NOW(INTERVAL '3' MINUTE) /* 
Evaluated to FROM_TIMESTAMP(TIMESTAMP '2020-12-12 23:21:12') at execution */\n"
+                        + "START_MODE = FROM_NOW(INTERVAL '3' MINUTE) /* 
Evaluated to FROM_TIMESTAMP(TIMESTAMP '2020-12-12 23:15:12') at execution */\n"
                         + "FRESHNESS = INTERVAL '1' MINUTE\n"
                         + "REFRESH_MODE = CONTINUOUS\n"
                         + "AS SELECT 1\n");
@@ -491,9 +515,7 @@ class ShowCreateUtilTest {
                         "Materialized table comment",
                         List.of("id"),
                         TableDistribution.of(TableDistribution.Kind.HASH, 5, 
List.of("id")),
-                        StartMode.of(
-                                StartModeKind.FROM_NOW,
-                                Interval.of(Period.of(0, 1, 2), 
TimeUnit.MONTH)),
+                        StartMode.of(StartModeKind.FROM_NOW, Interval.of(1, 
TimeUnit.MONTH)),
                         IntervalFreshness.ofMinute(3),
                         RefreshMode.FULL,
                         "SELECT * FROM tbl_a",
@@ -505,7 +527,7 @@ class ShowCreateUtilTest {
                         + "COMMENT 'Materialized table comment'\n"
                         + "DISTRIBUTED BY HASH(`id`) INTO 5 BUCKETS\n"
                         + "PARTITIONED BY (`id`)\n"
-                        + "START_MODE = FROM_NOW(INTERVAL '1' MONTH) /* 
Evaluated to FROM_TIMESTAMP(TIMESTAMP '1970-01-02 12:34:56') at execution */\n"
+                        + "START_MODE = FROM_NOW(INTERVAL '1' MONTH) /* 
Evaluated to FROM_TIMESTAMP(TIMESTAMP '2020-11-12 23:18:12') at execution */\n"
                         + "FRESHNESS = INTERVAL '3' MINUTE\n"
                         + "REFRESH_MODE = FULL\n"
                         + "AS SELECT id, name FROM 
`catalogName`.`dbName`.`tbl_a`\n");
@@ -652,13 +674,4 @@ class ShowCreateUtilTest {
                 freshness,
                 startMode);
     }
-
-    private static String setFixedTimestamp(String sql, String fixedTimestamp) 
{
-        return START_MODE_EVALUATED_TIMESTAMP
-                .matcher(sql)
-                .replaceAll(
-                        "/* Evaluated to FROM_TIMESTAMP(TIMESTAMP '"
-                                + fixedTimestamp
-                                + "') at execution */");
-    }
 }

Reply via email to