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

RocMarshal 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 1ea8cb0e4e5 [FLINK-40167][table] Add EARLY_FIRE join hint surface and 
option validation (#28353)
1ea8cb0e4e5 is described below

commit 1ea8cb0e4e53462d3196cee35eba954930847bb0
Author: Weiqing Yang <[email protected]>
AuthorDate: Fri Jul 24 18:12:25 2026 -0700

    [FLINK-40167][table] Add EARLY_FIRE join hint surface and option validation 
(#28353)
---
 .../docs/util/ConfigurationOptionLocator.java      |   3 +-
 .../table/api/config/EarlyFireJoinHintOptions.java |  81 +++++++++++
 .../planner/hint/CapitalizeQueryHintsShuttle.java  |   3 +-
 .../table/planner/hint/FlinkHintStrategies.java    |  58 ++++++++
 .../flink/table/planner/hint/JoinStrategy.java     |  13 ++
 .../planner/plan/optimize/QueryHintsResolver.java  |   7 +
 .../planner/plan/hints/batch/JoinHintTestBase.java |   5 +-
 .../plan/hints/stream/EarlyFireJoinHintTest.java   | 153 +++++++++++++++++++++
 .../plan/hints/stream/EarlyFireJoinHintTest.xml    |  51 +++++++
 9 files changed, 370 insertions(+), 4 deletions(-)

diff --git 
a/flink-docs/src/main/java/org/apache/flink/docs/util/ConfigurationOptionLocator.java
 
b/flink-docs/src/main/java/org/apache/flink/docs/util/ConfigurationOptionLocator.java
index 95dae776d7d..ed2d987be50 100644
--- 
a/flink-docs/src/main/java/org/apache/flink/docs/util/ConfigurationOptionLocator.java
+++ 
b/flink-docs/src/main/java/org/apache/flink/docs/util/ConfigurationOptionLocator.java
@@ -105,7 +105,8 @@ public class ConfigurationOptionLocator {
                             "org.apache.flink.state.rocksdb.PredefinedOptions",
                             "org.apache.flink.python.PythonConfig",
                             
"org.apache.flink.cep.configuration.SharedBufferCacheConfig",
-                            
"org.apache.flink.table.api.config.LookupJoinHintOptions"));
+                            
"org.apache.flink.table.api.config.LookupJoinHintOptions",
+                            
"org.apache.flink.table.api.config.EarlyFireJoinHintOptions"));
 
     private static final String DEFAULT_PATH_PREFIX = "src/main/java";
 
diff --git 
a/flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/api/config/EarlyFireJoinHintOptions.java
 
b/flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/api/config/EarlyFireJoinHintOptions.java
new file mode 100644
index 00000000000..6c919f318f2
--- /dev/null
+++ 
b/flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/api/config/EarlyFireJoinHintOptions.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.table.api.config;
+
+import org.apache.flink.annotation.PublicEvolving;
+import org.apache.flink.configuration.ConfigOption;
+
+import org.apache.flink.shaded.guava33.com.google.common.collect.ImmutableSet;
+
+import java.time.Duration;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.apache.flink.configuration.ConfigOptions.key;
+
+/**
+ * This class holds hint option name definitions for EARLY_FIRE join hints 
based on {@link
+ * org.apache.flink.configuration.ConfigOption}.
+ */
+@PublicEvolving
+public class EarlyFireJoinHintOptions {
+
+    public static final ConfigOption<Duration> DELAY =
+            key("delay")
+                    .durationType()
+                    .noDefaultValue()
+                    .withDescription(
+                            "The delay between the time an unmatched outer row 
becomes eligible to"
+                                    + " be emitted with null padding and the 
time it is actually"
+                                    + " emitted. Must be at least 1 
millisecond.");
+
+    public static final ConfigOption<TimeMode> TIME_MODE =
+            key("time-mode")
+                    .enumType(TimeMode.class)
+                    .noDefaultValue()
+                    .withDescription(
+                            "The time domain that drives the early-fire delay, 
can be 'rowtime' or"
+                                    + " 'proctime'. If not set, it defaults to 
the time domain of"
+                                    + " the interval join.");
+
+    private static final Set<ConfigOption<?>> requiredKeys = new HashSet<>();
+    private static final Set<ConfigOption<?>> supportedKeys = new HashSet<>();
+
+    static {
+        requiredKeys.add(DELAY);
+
+        supportedKeys.add(DELAY);
+        supportedKeys.add(TIME_MODE);
+    }
+
+    public static ImmutableSet<ConfigOption> getRequiredOptions() {
+        return ImmutableSet.copyOf(requiredKeys);
+    }
+
+    public static ImmutableSet<ConfigOption> getSupportedOptions() {
+        return ImmutableSet.copyOf(supportedKeys);
+    }
+
+    /** The time domain that drives the early-fire delay. */
+    @PublicEvolving
+    public enum TimeMode {
+        ROWTIME,
+        PROCTIME
+    }
+}
diff --git 
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/hint/CapitalizeQueryHintsShuttle.java
 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/hint/CapitalizeQueryHintsShuttle.java
index 006dab4b832..6da4452010d 100644
--- 
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/hint/CapitalizeQueryHintsShuttle.java
+++ 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/hint/CapitalizeQueryHintsShuttle.java
@@ -46,7 +46,8 @@ public class CapitalizeQueryHintsShuttle extends 
QueryHintsRelShuttle {
 
                                     changed.set(true);
                                     if 
(JoinStrategy.isJoinStrategy(capitalHintName)) {
-                                        if 
(JoinStrategy.isLookupHint(hint.hintName)) {
+                                        if 
(JoinStrategy.isLookupHint(hint.hintName)
+                                                || 
JoinStrategy.isEarlyFireHint(hint.hintName)) {
                                             return 
RelHint.builder(capitalHintName)
                                                     
.hintOptions(hint.kvOptions)
                                                     
.inheritPath(hint.inheritPath)
diff --git 
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/hint/FlinkHintStrategies.java
 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/hint/FlinkHintStrategies.java
index 5978f98017d..204c23a97d8 100644
--- 
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/hint/FlinkHintStrategies.java
+++ 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/hint/FlinkHintStrategies.java
@@ -20,6 +20,7 @@ package org.apache.flink.table.planner.hint;
 
 import org.apache.flink.configuration.ConfigOption;
 import org.apache.flink.configuration.Configuration;
+import org.apache.flink.table.api.config.EarlyFireJoinHintOptions;
 import org.apache.flink.table.api.config.LookupJoinHintOptions;
 import org.apache.flink.table.factories.FactoryUtil;
 import 
org.apache.flink.table.planner.plan.rules.logical.WrapJsonAggFunctionArgumentsRule;
@@ -36,6 +37,9 @@ import org.apache.calcite.util.Litmus;
 import java.time.Duration;
 import java.util.Collections;
 import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
 
 /**
  * A collection of Flink style {@link HintStrategy}s.
@@ -135,6 +139,11 @@ public abstract class FlinkHintStrategies {
                                                 HintPredicates.JOIN, 
HintPredicates.AGGREGATE))
                                 
.optionChecker(STATE_TTL_NON_EMPTY_KV_OPTION_CHECKER)
                                 .build())
+                .hintStrategy(
+                        JoinStrategy.EARLY_FIRE.getJoinHintName(),
+                        HintStrategy.builder(HintPredicates.JOIN)
+                                .optionChecker(EARLY_FIRE_KV_OPTION_CHECKER)
+                                .build())
                 .build();
     }
 
@@ -253,6 +262,55 @@ public abstract class FlinkHintStrategies {
                 return true;
             };
 
+    private static final HintOptionChecker EARLY_FIRE_KV_OPTION_CHECKER =
+            (earlyFireHint, litmus) -> {
+                litmus.check(
+                        earlyFireHint.listOptions.size() == 0,
+                        "Invalid list options in EARLY_FIRE hint, only support 
key-value options.");
+
+                Configuration conf = 
Configuration.fromMap(earlyFireHint.kvOptions);
+                ImmutableSet<ConfigOption> requiredKeys =
+                        EarlyFireJoinHintOptions.getRequiredOptions();
+                litmus.check(
+                        requiredKeys.stream().allMatch(conf::contains),
+                        "Invalid EARLY_FIRE hint: incomplete required 
option(s): {}",
+                        requiredKeys);
+
+                ImmutableSet<ConfigOption> supportedKeys =
+                        EarlyFireJoinHintOptions.getSupportedOptions();
+                Set<String> supportedKeyNames =
+                        
supportedKeys.stream().map(ConfigOption::key).collect(Collectors.toSet());
+                Set<String> unknownKeys =
+                        earlyFireHint.kvOptions.keySet().stream()
+                                .filter(key -> 
!supportedKeyNames.contains(key))
+                                
.collect(Collectors.toCollection(TreeSet::new));
+                litmus.check(
+                        unknownKeys.isEmpty(),
+                        "Unsupported EARLY_FIRE hint option(s) {}, supported 
options are {}.",
+                        unknownKeys,
+                        new TreeSet<>(supportedKeyNames));
+                litmus.check(
+                        earlyFireHint.kvOptions.size() <= supportedKeys.size(),
+                        "Too many EARLY_FIRE hint options {} beyond max number 
of supported options {}",
+                        earlyFireHint.kvOptions.size(),
+                        supportedKeys.size());
+
+                try {
+                    // try to validate all hint options by parsing them
+                    supportedKeys.forEach(conf::get);
+                } catch (IllegalArgumentException e) {
+                    litmus.fail("Invalid EARLY_FIRE hint options: {}", 
e.getMessage());
+                }
+
+                Duration delay = conf.get(EarlyFireJoinHintOptions.DELAY);
+                litmus.check(
+                        null != delay && delay.toMillis() > 0,
+                        "Invalid EARLY_FIRE hint option: {} value should be at 
least 1 millisecond but was {}",
+                        EarlyFireJoinHintOptions.DELAY.key(),
+                        delay);
+                return true;
+            };
+
     private static final HintOptionChecker 
STATE_TTL_NON_EMPTY_KV_OPTION_CHECKER =
             (ttlHint, litmus) -> {
                 litmus.check(
diff --git 
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/hint/JoinStrategy.java
 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/hint/JoinStrategy.java
index 0ded47cb222..d9e3acff7a2 100644
--- 
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/hint/JoinStrategy.java
+++ 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/hint/JoinStrategy.java
@@ -50,6 +50,12 @@ public enum JoinStrategy {
     /** Instructs the optimizer to use lookup join strategy. Only accept 
key-value hint options. */
     LOOKUP("LOOKUP"),
 
+    /**
+     * Instructs an outer interval join to emit unmatched outer rows with null 
padding after a
+     * configurable delay. Only accept key-value hint options.
+     */
+    EARLY_FIRE("EARLY_FIRE"),
+
     /**
      * Instructs the optimizer to use multi-way join strategy for streaming 
queries. This hint
      * allows specifying multiple tables to be joined together in a single 
{@link
@@ -89,6 +95,7 @@ public enum JoinStrategy {
             case NEST_LOOP:
                 return options.size() > 0;
             case LOOKUP:
+            case EARLY_FIRE:
                 return null == options || options.size() == 0;
             case MULTI_JOIN:
                 return options.size() > 0;
@@ -101,4 +108,10 @@ public enum JoinStrategy {
         return isJoinStrategy(formalizedHintName)
                 && JoinStrategy.valueOf(formalizedHintName) == LOOKUP;
     }
+
+    public static boolean isEarlyFireHint(String hintName) {
+        String formalizedHintName = hintName.toUpperCase(Locale.ROOT);
+        return isJoinStrategy(formalizedHintName)
+                && JoinStrategy.valueOf(formalizedHintName) == EARLY_FIRE;
+    }
 }
diff --git 
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/optimize/QueryHintsResolver.java
 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/optimize/QueryHintsResolver.java
index b288f111e30..b153ef2b885 100644
--- 
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/optimize/QueryHintsResolver.java
+++ 
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/optimize/QueryHintsResolver.java
@@ -146,6 +146,13 @@ public class QueryHintsResolver extends 
QueryHintsRelShuttle {
                     updateInfoForOptionCheck(hint.hintName, rightName);
                     newHints.add(hint);
                 }
+            } else if (JoinStrategy.isEarlyFireHint(hint.hintName)) {
+                // EARLY_FIRE carries only key-value options and is not bound 
to a specific input
+                // side, so it is passed through unchanged once its options 
are validated by the
+                // hint option checker.
+                allHints.add(trimInheritPath(hint));
+                validHints.add(trimInheritPath(hint));
+                newHints.add(hint);
             } else if (JoinStrategy.isJoinStrategy(hint.hintName)) {
                 allHints.add(trimInheritPath(hint));
                 // add options about this hint for finally checking
diff --git 
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/hints/batch/JoinHintTestBase.java
 
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/hints/batch/JoinHintTestBase.java
index 24ae0e4025d..217e3c2b8af 100644
--- 
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/hints/batch/JoinHintTestBase.java
+++ 
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/hints/batch/JoinHintTestBase.java
@@ -61,8 +61,9 @@ public abstract class JoinHintTestBase extends TableTestBase {
 
     private final List<String> allJoinHintNames =
             Lists.newArrayList(JoinStrategy.values()).stream()
-                    // LOOKUP hint has different kv-options against other join 
hints
-                    .filter(hint -> hint != JoinStrategy.LOOKUP)
+                    // LOOKUP and EARLY_FIRE hints only support key-value 
options, unlike the
+                    // list-option join hints exercised here
+                    .filter(hint -> hint != JoinStrategy.LOOKUP && hint != 
JoinStrategy.EARLY_FIRE)
                     .map(JoinStrategy::getJoinHintName)
                     .collect(Collectors.toList());
 
diff --git 
a/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/hints/stream/EarlyFireJoinHintTest.java
 
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/hints/stream/EarlyFireJoinHintTest.java
new file mode 100644
index 00000000000..447c198eef2
--- /dev/null
+++ 
b/flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/plan/hints/stream/EarlyFireJoinHintTest.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.table.planner.plan.hints.stream;
+
+import org.apache.flink.table.api.ExplainDetail;
+import org.apache.flink.table.api.TableConfig;
+import org.apache.flink.table.planner.utils.PlanKind;
+import org.apache.flink.table.planner.utils.StreamTableTestUtil;
+import org.apache.flink.table.planner.utils.TableTestBase;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import scala.Enumeration;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/** Test for the EARLY_FIRE join hint surface and option validation. */
+class EarlyFireJoinHintTest extends TableTestBase {
+
+    protected StreamTableTestUtil util;
+
+    @BeforeEach
+    void before() {
+        util = streamTestUtil(TableConfig.getDefault());
+        util.tableEnv()
+                .executeSql(
+                        "CREATE TABLE MyTable (\n"
+                                + "  a INT,\n"
+                                + "  b VARCHAR,\n"
+                                + "  c BIGINT,\n"
+                                + "  proctime AS PROCTIME(),\n"
+                                + "  rowtime TIMESTAMP(3),\n"
+                                + "  WATERMARK FOR rowtime AS rowtime\n"
+                                + ") WITH (\n"
+                                + "  'connector' = 'values',\n"
+                                + "  'bounded' = 'false'\n"
+                                + ")");
+        util.tableEnv()
+                .executeSql(
+                        "CREATE TABLE MyTable2 (\n"
+                                + "  a INT,\n"
+                                + "  b VARCHAR,\n"
+                                + "  c BIGINT,\n"
+                                + "  proctime AS PROCTIME(),\n"
+                                + "  rowtime TIMESTAMP(3),\n"
+                                + "  WATERMARK FOR rowtime AS rowtime\n"
+                                + ") WITH (\n"
+                                + "  'connector' = 'values',\n"
+                                + "  'bounded' = 'false'\n"
+                                + ")");
+    }
+
+    @Test
+    void testEarlyFireMissingDelay() {
+        String sql =
+                "SELECT /*+ EARLY_FIRE('time-mode'='rowtime') */ t1.a, t2.b\n"
+                        + "FROM MyTable t1 LEFT OUTER JOIN MyTable2 t2 ON\n"
+                        + "  t1.a = t2.a AND\n"
+                        + "  t1.rowtime BETWEEN t2.rowtime - INTERVAL '10' 
SECOND AND t2.rowtime + INTERVAL '1' HOUR";
+        assertThatThrownBy(() -> verify(sql)).hasMessageContaining("incomplete 
required option(s)");
+    }
+
+    @Test
+    void testEarlyFireNonPositiveDelay() {
+        String sql =
+                "SELECT /*+ EARLY_FIRE('delay'='0s') */ t1.a, t2.b\n"
+                        + "FROM MyTable t1 LEFT OUTER JOIN MyTable2 t2 ON\n"
+                        + "  t1.a = t2.a AND\n"
+                        + "  t1.rowtime BETWEEN t2.rowtime - INTERVAL '10' 
SECOND AND t2.rowtime + INTERVAL '1' HOUR";
+        assertThatThrownBy(() -> verify(sql))
+                .hasMessageContaining("value should be at least 1 
millisecond");
+    }
+
+    @Test
+    void testEarlyFireSubMillisecondDelay() {
+        String sql =
+                "SELECT /*+ EARLY_FIRE('delay'='1ns') */ t1.a, t2.b\n"
+                        + "FROM MyTable t1 LEFT OUTER JOIN MyTable2 t2 ON\n"
+                        + "  t1.a = t2.a AND\n"
+                        + "  t1.rowtime BETWEEN t2.rowtime - INTERVAL '10' 
SECOND AND t2.rowtime + INTERVAL '1' HOUR";
+        assertThatThrownBy(() -> verify(sql))
+                .hasMessageContaining("value should be at least 1 
millisecond");
+    }
+
+    @Test
+    void testEarlyFireInvalidTimeMode() {
+        String sql =
+                "SELECT /*+ EARLY_FIRE('delay'='5s', 'time-mode'='unknown') */ 
t1.a, t2.b\n"
+                        + "FROM MyTable t1 LEFT OUTER JOIN MyTable2 t2 ON\n"
+                        + "  t1.a = t2.a AND\n"
+                        + "  t1.rowtime BETWEEN t2.rowtime - INTERVAL '10' 
SECOND AND t2.rowtime + INTERVAL '1' HOUR";
+        assertThatThrownBy(() -> verify(sql))
+                .hasMessageContaining("Invalid EARLY_FIRE hint options");
+    }
+
+    @Test
+    void testEarlyFireUnknownOption() {
+        String sql =
+                "SELECT /*+ EARLY_FIRE('delay'='5s', 'timemode'='proctime') */ 
t1.a, t2.b\n"
+                        + "FROM MyTable t1 LEFT OUTER JOIN MyTable2 t2 ON\n"
+                        + "  t1.a = t2.a AND\n"
+                        + "  t1.rowtime BETWEEN t2.rowtime - INTERVAL '10' 
SECOND AND t2.rowtime + INTERVAL '1' HOUR";
+        assertThatThrownBy(() -> verify(sql))
+                .hasMessageContaining("Unsupported EARLY_FIRE hint option(s) 
[timemode]");
+    }
+
+    @Test
+    void testEarlyFireListOptionsRejected() {
+        String sql =
+                "SELECT /*+ EARLY_FIRE('5s') */ t1.a, t2.b\n"
+                        + "FROM MyTable t1 LEFT OUTER JOIN MyTable2 t2 ON\n"
+                        + "  t1.a = t2.a AND\n"
+                        + "  t1.rowtime BETWEEN t2.rowtime - INTERVAL '10' 
SECOND AND t2.rowtime + INTERVAL '1' HOUR";
+        assertThatThrownBy(() -> verify(sql))
+                .hasMessageContaining("only support key-value options");
+    }
+
+    @Test
+    void testEarlyFireLowerCaseHintNamePreservesOptions() {
+        String sql =
+                "SELECT /*+ early_fire('delay'='5s', 'time-mode'='rowtime') */ 
t1.a, t2.b\n"
+                        + "FROM MyTable t1 LEFT OUTER JOIN MyTable2 t2 ON\n"
+                        + "  t1.a = t2.a AND\n"
+                        + "  t1.rowtime BETWEEN t2.rowtime - INTERVAL '10' 
SECOND AND t2.rowtime + INTERVAL '1' HOUR";
+        verify(sql);
+    }
+
+    private void verify(String sql) {
+        util.doVerifyPlan(
+                sql,
+                new ExplainDetail[] {},
+                false,
+                new Enumeration.Value[] {PlanKind.AST(), PlanKind.OPT_EXEC()},
+                false);
+    }
+}
diff --git 
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/hints/stream/EarlyFireJoinHintTest.xml
 
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/hints/stream/EarlyFireJoinHintTest.xml
new file mode 100644
index 00000000000..df5bc8675e7
--- /dev/null
+++ 
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/hints/stream/EarlyFireJoinHintTest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" ?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to you under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<Root>
+  <TestCase name="testEarlyFireLowerCaseHintNamePreservesOptions">
+    <Resource name="sql">
+      <![CDATA[SELECT /*+ early_fire('delay'='5s', 'time-mode'='rowtime') */ 
t1.a, t2.b
+FROM MyTable t1 LEFT OUTER JOIN MyTable2 t2 ON
+  t1.a = t2.a AND
+  t1.rowtime BETWEEN t2.rowtime - INTERVAL '10' SECOND AND t2.rowtime + 
INTERVAL '1' HOUR]]>
+    </Resource>
+    <Resource name="ast">
+      <![CDATA[
+LogicalProject(a=[$0], b=[$6])
++- LogicalJoin(condition=[AND(=($0, $5), >=($4, -($9, 10000:INTERVAL SECOND)), 
<=($4, +($9, 3600000:INTERVAL HOUR)))], joinType=[left], 
joinHints=[[[EARLY_FIRE inheritPath:[0] options:{delay=5s, 
time-mode=rowtime}]]])
+   :- LogicalWatermarkAssigner(rowtime=[rowtime], watermark=[$4])
+   :  +- LogicalProject(a=[$0], b=[$1], c=[$2], proctime=[PROCTIME()], 
rowtime=[$3])
+   :     +- LogicalTableScan(table=[[default_catalog, default_database, 
MyTable]])
+   +- LogicalWatermarkAssigner(rowtime=[rowtime], watermark=[$4])
+      +- LogicalProject(a=[$0], b=[$1], c=[$2], proctime=[PROCTIME()], 
rowtime=[$3])
+         +- LogicalTableScan(table=[[default_catalog, default_database, 
MyTable2]])
+]]>
+    </Resource>
+    <Resource name="optimized exec plan">
+      <![CDATA[
+Calc(select=[a, b])
++- IntervalJoin(joinType=[LeftOuterJoin], windowBounds=[isRowTime=true, 
leftLowerBound=-10000, leftUpperBound=3600000, leftTimeIndex=1, 
rightTimeIndex=2], where=[((a = a0) AND (rowtime >= (rowtime0 - 10000:INTERVAL 
SECOND)) AND (rowtime <= (rowtime0 + 3600000:INTERVAL HOUR)))], select=[a, 
rowtime, a0, b, rowtime0])
+   :- Exchange(distribution=[hash[a]])
+   :  +- WatermarkAssigner(rowtime=[rowtime], watermark=[rowtime])
+   :     +- TableSourceScan(table=[[default_catalog, default_database, 
MyTable, project=[a, rowtime], metadata=[]]], fields=[a, rowtime])
+   +- Exchange(distribution=[hash[a]])
+      +- WatermarkAssigner(rowtime=[rowtime], watermark=[rowtime])
+         +- TableSourceScan(table=[[default_catalog, default_database, 
MyTable2, project=[a, b, rowtime], metadata=[]]], fields=[a, b, rowtime])
+]]>
+    </Resource>
+  </TestCase>
+</Root>

Reply via email to