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 78358442b68 [FLINK-40168][table] Thread the EARLY_FIRE hint into the
interval join
78358442b68 is described below
commit 78358442b68bfc4ee99e02f7f6aaac17e20cc175
Author: weiqingy <[email protected]>
AuthorDate: Sat Jul 18 15:11:00 2026 -0700
[FLINK-40168][table] Thread the EARLY_FIRE hint into the interval join
---
.../nodes/exec/stream/StreamExecIntervalJoin.java | 25 ++
.../stream/StreamPhysicalIntervalJoinRule.java | 59 ++-
.../stream/StreamPhysicalIntervalJoin.scala | 13 +-
.../plan/hints/stream/EarlyFireJoinHintTest.java | 60 +++
.../plan/hints/stream/EarlyFireJoinHintTest.xml | 70 +++-
.../testEarlyFireJsonPlanRoundTrip.out | 446 +++++++++++++++++++++
6 files changed, 669 insertions(+), 4 deletions(-)
diff --git
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/nodes/exec/stream/StreamExecIntervalJoin.java
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/nodes/exec/stream/StreamExecIntervalJoin.java
index 2ac0af781e0..20b676af785 100644
---
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/nodes/exec/stream/StreamExecIntervalJoin.java
+++
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/nodes/exec/stream/StreamExecIntervalJoin.java
@@ -28,6 +28,7 @@ import
org.apache.flink.streaming.api.transformations.OneInputTransformation;
import org.apache.flink.streaming.api.transformations.TwoInputTransformation;
import org.apache.flink.streaming.api.transformations.UnionTransformation;
import org.apache.flink.table.api.TableException;
+import org.apache.flink.table.api.config.EarlyFireJoinHintOptions;
import org.apache.flink.table.api.config.ExecutionConfigOptions;
import org.apache.flink.table.data.RowData;
import org.apache.flink.table.planner.delegation.PlannerBase;
@@ -59,11 +60,14 @@ import org.apache.flink.util.Preconditions;
import org.apache.flink.shaded.guava33.com.google.common.collect.Lists;
import
org.apache.flink.shaded.jackson2.com.fasterxml.jackson.annotation.JsonCreator;
+import
org.apache.flink.shaded.jackson2.com.fasterxml.jackson.annotation.JsonInclude;
import
org.apache.flink.shaded.jackson2.com.fasterxml.jackson.annotation.JsonProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.annotation.Nullable;
+
import java.util.List;
/** {@link StreamExecNode} for a time interval stream join. */
@@ -91,13 +95,27 @@ public class StreamExecIntervalJoin extends
ExecNodeBase<RowData>
public static final String INTERVAL_JOIN_TRANSFORMATION = "interval-join";
public static final String FIELD_NAME_INTERVAL_JOIN_SPEC =
"intervalJoinSpec";
+ public static final String FIELD_NAME_EARLY_FIRE_DELAY = "earlyFireDelay";
+ public static final String FIELD_NAME_EARLY_FIRE_TIME_MODE =
"earlyFireTimeMode";
@JsonProperty(FIELD_NAME_INTERVAL_JOIN_SPEC)
private final IntervalJoinSpec intervalJoinSpec;
+ @Nullable
+ @JsonProperty(FIELD_NAME_EARLY_FIRE_DELAY)
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ private final Long earlyFireDelay;
+
+ @Nullable
+ @JsonProperty(FIELD_NAME_EARLY_FIRE_TIME_MODE)
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ private final EarlyFireJoinHintOptions.TimeMode earlyFireTimeMode;
+
public StreamExecIntervalJoin(
ReadableConfig tableConfig,
IntervalJoinSpec intervalJoinSpec,
+ @Nullable Long earlyFireDelay,
+ @Nullable EarlyFireJoinHintOptions.TimeMode earlyFireTimeMode,
InputProperty leftInputProperty,
InputProperty rightInputProperty,
RowType outputType,
@@ -107,6 +125,8 @@ public class StreamExecIntervalJoin extends
ExecNodeBase<RowData>
ExecNodeContext.newContext(StreamExecIntervalJoin.class),
ExecNodeContext.newPersistedConfig(StreamExecIntervalJoin.class, tableConfig),
intervalJoinSpec,
+ earlyFireDelay,
+ earlyFireTimeMode,
Lists.newArrayList(leftInputProperty, rightInputProperty),
outputType,
description);
@@ -118,12 +138,17 @@ public class StreamExecIntervalJoin extends
ExecNodeBase<RowData>
@JsonProperty(FIELD_NAME_TYPE) ExecNodeContext context,
@JsonProperty(FIELD_NAME_CONFIGURATION) ReadableConfig
persistedConfig,
@JsonProperty(FIELD_NAME_INTERVAL_JOIN_SPEC) IntervalJoinSpec
intervalJoinSpec,
+ @Nullable @JsonProperty(FIELD_NAME_EARLY_FIRE_DELAY) Long
earlyFireDelay,
+ @Nullable @JsonProperty(FIELD_NAME_EARLY_FIRE_TIME_MODE)
+ EarlyFireJoinHintOptions.TimeMode earlyFireTimeMode,
@JsonProperty(FIELD_NAME_INPUT_PROPERTIES) List<InputProperty>
inputProperties,
@JsonProperty(FIELD_NAME_OUTPUT_TYPE) RowType outputType,
@JsonProperty(FIELD_NAME_DESCRIPTION) String description) {
super(id, context, persistedConfig, inputProperties, outputType,
description);
Preconditions.checkArgument(inputProperties.size() == 2);
this.intervalJoinSpec = Preconditions.checkNotNull(intervalJoinSpec);
+ this.earlyFireDelay = earlyFireDelay;
+ this.earlyFireTimeMode = earlyFireTimeMode;
}
@Override
diff --git
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/physical/stream/StreamPhysicalIntervalJoinRule.java
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/physical/stream/StreamPhysicalIntervalJoinRule.java
index 6354f04de07..d7cbad27d0c 100644
---
a/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/physical/stream/StreamPhysicalIntervalJoinRule.java
+++
b/flink-table/flink-table-planner/src/main/java/org/apache/flink/table/planner/plan/rules/physical/stream/StreamPhysicalIntervalJoinRule.java
@@ -19,9 +19,13 @@
package org.apache.flink.table.planner.plan.rules.physical.stream;
import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.configuration.Configuration;
import org.apache.flink.table.api.TableException;
import org.apache.flink.table.api.ValidationException;
+import org.apache.flink.table.api.config.EarlyFireJoinHintOptions;
+import org.apache.flink.table.api.config.EarlyFireJoinHintOptions.TimeMode;
import org.apache.flink.table.planner.calcite.FlinkTypeFactory;
+import org.apache.flink.table.planner.hint.JoinStrategy;
import org.apache.flink.table.planner.plan.nodes.FlinkRelNode;
import org.apache.flink.table.planner.plan.nodes.exec.spec.IntervalJoinSpec;
import org.apache.flink.table.planner.plan.nodes.logical.FlinkLogicalJoin;
@@ -32,11 +36,16 @@ import org.apache.calcite.plan.RelOptRule;
import org.apache.calcite.plan.RelOptRuleCall;
import org.apache.calcite.plan.RelTraitSet;
import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.hint.RelHint;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rex.RexNode;
import org.immutables.value.Value;
+import javax.annotation.Nullable;
+
+import java.time.Duration;
import java.util.Collection;
+import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -133,6 +142,8 @@ public class StreamPhysicalIntervalJoinRule
RelTraitSet providedTraitSet) {
Tuple2<Option<IntervalJoinSpec.WindowBounds>, Option<RexNode>> tuple2 =
extractWindowBounds(join);
+ boolean isEventTime = tuple2.f0.get().isEventTime();
+ EarlyFire earlyFire = extractEarlyFire(join.getHints(), isEventTime);
return new StreamPhysicalIntervalJoin(
join.getCluster(),
providedTraitSet,
@@ -141,7 +152,53 @@ public class StreamPhysicalIntervalJoinRule
join.getJoinType(),
join.getCondition(),
tuple2.f1.getOrElse(() ->
join.getCluster().getRexBuilder().makeLiteral(true)),
- tuple2.f0.get());
+ tuple2.f0.get(),
+ earlyFire.delay,
+ earlyFire.timeMode);
+ }
+
+ private static EarlyFire extractEarlyFire(List<RelHint> hints, boolean
isEventTime) {
+ RelHint earlyFireHint = null;
+ for (RelHint hint : hints) {
+ if (JoinStrategy.isEarlyFireHint(hint.hintName)) {
+ earlyFireHint = hint;
+ break;
+ }
+ }
+ if (earlyFireHint == null) {
+ return new EarlyFire(null, null);
+ }
+
+ Configuration conf = Configuration.fromMap(earlyFireHint.kvOptions);
+ Duration delay = conf.get(EarlyFireJoinHintOptions.DELAY);
+ TimeMode timeMode = conf.get(EarlyFireJoinHintOptions.TIME_MODE);
+ if (timeMode == null) {
+ timeMode = isEventTime ? TimeMode.ROWTIME : TimeMode.PROCTIME;
+ }
+
+ if (!isEventTime && timeMode == TimeMode.ROWTIME) {
+ throw new ValidationException(
+ "EARLY_FIRE hint requested row-time triggering on a
processing-time interval"
+ + " join. Row-time triggering requires a row-time
interval join.");
+ }
+ if (isEventTime && timeMode == TimeMode.PROCTIME) {
+ // Processing-time triggering on an event-time interval join is
not supported.
+ throw new TableException(
+ "EARLY_FIRE hint requested processing-time triggering on a
row-time interval"
+ + " join, which is not yet supported.");
+ }
+
+ return new EarlyFire(delay == null ? null : delay.toMillis(),
timeMode);
+ }
+
+ private static final class EarlyFire {
+ @Nullable private final Long delay;
+ @Nullable private final TimeMode timeMode;
+
+ EarlyFire(@Nullable Long delay, @Nullable TimeMode timeMode) {
+ this.delay = delay;
+ this.timeMode = timeMode;
+ }
}
/** Configuration for {@link StreamPhysicalIntervalJoinRule}. */
diff --git
a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/nodes/physical/stream/StreamPhysicalIntervalJoin.scala
b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/nodes/physical/stream/StreamPhysicalIntervalJoin.scala
index 4916d653b2e..d3401783989 100644
---
a/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/nodes/physical/stream/StreamPhysicalIntervalJoin.scala
+++
b/flink-table/flink-table-planner/src/main/scala/org/apache/flink/table/planner/plan/nodes/physical/stream/StreamPhysicalIntervalJoin.scala
@@ -18,6 +18,7 @@
package org.apache.flink.table.planner.plan.nodes.physical.stream
import org.apache.flink.table.api.TableException
+import org.apache.flink.table.api.config.EarlyFireJoinHintOptions.TimeMode
import org.apache.flink.table.planner.calcite.FlinkTypeFactory
import org.apache.flink.table.planner.plan.nodes.exec.{ExecNode, InputProperty}
import org.apache.flink.table.planner.plan.nodes.exec.spec.IntervalJoinSpec
@@ -45,7 +46,9 @@ class StreamPhysicalIntervalJoin(
val originalCondition: RexNode,
// remaining join condition contains all of join condition except window
bounds
remainingCondition: RexNode,
- windowBounds: WindowBounds)
+ windowBounds: WindowBounds,
+ earlyFireDelay: java.lang.Long,
+ earlyFireTimeMode: TimeMode)
extends CommonPhysicalJoin(cluster, traitSet, leftRel, rightRel,
remainingCondition, joinType)
with StreamPhysicalRel {
@@ -76,7 +79,9 @@ class StreamPhysicalIntervalJoin(
joinType,
originalCondition,
conditionExpr,
- windowBounds)
+ windowBounds,
+ earlyFireDelay,
+ earlyFireTimeMode)
}
override def explainTerms(pw: RelWriter): RelWriter = {
@@ -98,12 +103,16 @@ class StreamPhysicalIntervalJoin(
preferExpressionFormat(pw),
pw.getDetailLevel))
.item("select", getRowType.getFieldNames.mkString(", "))
+ .itemIf("earlyFireDelay", earlyFireDelay, earlyFireDelay != null)
+ .itemIf("earlyFireTimeMode", earlyFireTimeMode, earlyFireTimeMode !=
null)
}
override def translateToExecNode(): ExecNode[_] = {
new StreamExecIntervalJoin(
unwrapTableConfig(this),
new IntervalJoinSpec(joinSpec, windowBounds),
+ earlyFireDelay,
+ earlyFireTimeMode,
InputProperty.DEFAULT,
InputProperty.DEFAULT,
FlinkTypeFactory.toLogicalRowType(getRowType),
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
index 447c198eef2..88aac40078f 100644
---
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
@@ -65,6 +65,14 @@ class EarlyFireJoinHintTest extends TableTestBase {
+ " 'connector' = 'values',\n"
+ " 'bounded' = 'false'\n"
+ ")");
+ util.tableEnv()
+ .executeSql(
+ "CREATE TABLE MySink (\n"
+ + " a INT,\n"
+ + " b VARCHAR\n"
+ + ") WITH (\n"
+ + " 'connector' = 'values'\n"
+ + ")");
}
@Test
@@ -142,6 +150,58 @@ class EarlyFireJoinHintTest extends TableTestBase {
verify(sql);
}
+ @Test
+ void testEarlyFireOnRowTimeLeftOuterJoin() {
+ String sql =
+ "SELECT /*+ EARLY_FIRE('delay'='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";
+ verify(sql);
+ }
+
+ @Test
+ void testEarlyFireRowTimeOnProcTimeJoin() {
+ 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.proctime BETWEEN t2.proctime - INTERVAL '1'
HOUR AND t2.proctime + INTERVAL '1' HOUR";
+ assertThatThrownBy(() -> verify(sql))
+ .hasStackTraceContaining("requires a row-time interval join");
+ }
+
+ @Test
+ void testEarlyFireProcTimeOnRowTimeJoin() {
+ String sql =
+ "SELECT /*+ EARLY_FIRE('delay'='5s', 'time-mode'='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)).hasStackTraceContaining("not yet
supported");
+ }
+
+ @Test
+ void testEarlyFireOnProcTimeLeftOuterJoin() {
+ String sql =
+ "SELECT /*+ EARLY_FIRE('delay'='5s') */ t1.a, t2.b\n"
+ + "FROM MyTable t1 LEFT OUTER JOIN MyTable2 t2 ON\n"
+ + " t1.a = t2.a AND\n"
+ + " t1.proctime BETWEEN t2.proctime - INTERVAL '1'
HOUR AND t2.proctime + INTERVAL '1' HOUR";
+ verify(sql);
+ }
+
+ @Test
+ void testEarlyFireJsonPlanRoundTrip() {
+ String insert =
+ "INSERT INTO MySink\n"
+ + "SELECT /*+ EARLY_FIRE('delay'='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";
+ util.verifyJsonPlan(insert);
+ }
+
private void verify(String sql) {
util.doVerifyPlan(
sql,
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
index df5bc8675e7..3df60f3cb8d 100644
---
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
@@ -38,7 +38,75 @@ LogicalProject(a=[$0], b=[$6])
<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])
++- 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], earlyFireDelay=[5000], earlyFireTimeMode=[ROWTIME])
+ :- 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>
+ <TestCase name="testEarlyFireOnProcTimeLeftOuterJoin">
+ <Resource name="sql">
+ <![CDATA[SELECT /*+ EARLY_FIRE('delay'='5s') */ t1.a, t2.b
+FROM MyTable t1 LEFT OUTER JOIN MyTable2 t2 ON
+ t1.a = t2.a AND
+ t1.proctime BETWEEN t2.proctime - INTERVAL '1' HOUR AND t2.proctime +
INTERVAL '1' HOUR]]>
+ </Resource>
+ <Resource name="ast">
+ <![CDATA[
+LogicalProject(a=[$0], b=[$6])
++- LogicalJoin(condition=[AND(=($0, $5), >=($3, -($8, 3600000:INTERVAL HOUR)),
<=($3, +($8, 3600000:INTERVAL HOUR)))], joinType=[left],
joinHints=[[[EARLY_FIRE inheritPath:[0] options:{delay=5s}]]])
+ :- 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=false,
leftLowerBound=-3600000, leftUpperBound=3600000, leftTimeIndex=1,
rightTimeIndex=2], where=[((a = a0) AND (proctime >= (proctime0 -
3600000:INTERVAL HOUR)) AND (proctime <= (proctime0 + 3600000:INTERVAL
HOUR)))], select=[a, proctime, a0, b, proctime0], earlyFireDelay=[5000],
earlyFireTimeMode=[PROCTIME])
+ :- Exchange(distribution=[hash[a]])
+ : +- Calc(select=[a, proctime])
+ : +- WatermarkAssigner(rowtime=[rowtime], watermark=[rowtime])
+ : +- Calc(select=[a, PROCTIME() AS proctime, rowtime])
+ : +- TableSourceScan(table=[[default_catalog, default_database,
MyTable, project=[a, rowtime], metadata=[]]], fields=[a, rowtime])
+ +- Exchange(distribution=[hash[a]])
+ +- Calc(select=[a, b, proctime])
+ +- WatermarkAssigner(rowtime=[rowtime], watermark=[rowtime])
+ +- Calc(select=[a, b, PROCTIME() AS proctime, rowtime])
+ +- TableSourceScan(table=[[default_catalog, default_database,
MyTable2, project=[a, b, rowtime], metadata=[]]], fields=[a, b, rowtime])
+]]>
+ </Resource>
+ </TestCase>
+ <TestCase name="testEarlyFireOnRowTimeLeftOuterJoin">
+ <Resource name="sql">
+ <![CDATA[SELECT /*+ EARLY_FIRE('delay'='5s') */ 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}]]])
+ :- 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], earlyFireDelay=[5000], earlyFireTimeMode=[ROWTIME])
:- Exchange(distribution=[hash[a]])
: +- WatermarkAssigner(rowtime=[rowtime], watermark=[rowtime])
: +- TableSourceScan(table=[[default_catalog, default_database,
MyTable, project=[a, rowtime], metadata=[]]], fields=[a, rowtime])
diff --git
a/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/hints/stream/EarlyFireJoinHintTest_jsonplan/testEarlyFireJsonPlanRoundTrip.out
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/hints/stream/EarlyFireJoinHintTest_jsonplan/testEarlyFireJsonPlanRoundTrip.out
new file mode 100644
index 00000000000..2b98b28751e
--- /dev/null
+++
b/flink-table/flink-table-planner/src/test/resources/org/apache/flink/table/planner/plan/hints/stream/EarlyFireJoinHintTest_jsonplan/testEarlyFireJsonPlanRoundTrip.out
@@ -0,0 +1,446 @@
+{
+ "flinkVersion" : "",
+ "nodes" : [ {
+ "id" : 1,
+ "type" : "stream-exec-table-source-scan_2",
+ "scanTableSource" : {
+ "table" : {
+ "identifier" : "`default_catalog`.`default_database`.`MyTable`",
+ "resolvedTable" : {
+ "schema" : {
+ "columns" : [ {
+ "name" : "a",
+ "dataType" : "INT"
+ }, {
+ "name" : "b",
+ "dataType" : "VARCHAR(2147483647)"
+ }, {
+ "name" : "c",
+ "dataType" : "BIGINT"
+ }, {
+ "name" : "proctime",
+ "kind" : "COMPUTED",
+ "expression" : {
+ "rexNode" : {
+ "kind" : "CALL",
+ "internalName" : "$PROCTIME$1",
+ "type" : {
+ "type" : "TIMESTAMP_WITH_LOCAL_TIME_ZONE",
+ "nullable" : false,
+ "precision" : 3,
+ "kind" : "PROCTIME"
+ }
+ },
+ "serializableString" : "PROCTIME()"
+ }
+ }, {
+ "name" : "rowtime",
+ "dataType" : {
+ "type" : "TIMESTAMP_WITHOUT_TIME_ZONE",
+ "precision" : 3,
+ "kind" : "ROWTIME"
+ }
+ } ],
+ "watermarkSpecs" : [ {
+ "rowtimeAttribute" : "rowtime",
+ "expression" : {
+ "rexNode" : {
+ "kind" : "INPUT_REF",
+ "inputIndex" : 4,
+ "type" : "TIMESTAMP(3)"
+ },
+ "serializableString" : "`rowtime`"
+ }
+ } ]
+ },
+ "options" : {
+ "bounded" : "false",
+ "connector" : "values"
+ }
+ }
+ },
+ "abilities" : [ {
+ "type" : "ProjectPushDown",
+ "projectedFields" : [ [ 0 ], [ 3 ] ],
+ "producedType" : "ROW<`a` INT, `rowtime` TIMESTAMP(3)> NOT NULL"
+ }, {
+ "type" : "ReadingMetadata",
+ "metadataKeys" : [ ],
+ "producedType" : "ROW<`a` INT, `rowtime` TIMESTAMP(3)> NOT NULL"
+ } ]
+ },
+ "outputType" : "ROW<`a` INT, `rowtime` TIMESTAMP(3)>",
+ "description" : "TableSourceScan(table=[[default_catalog,
default_database, MyTable, project=[a, rowtime], metadata=[]]], fields=[a,
rowtime])"
+ }, {
+ "id" : 2,
+ "type" : "stream-exec-watermark-assigner_1",
+ "watermarkExpr" : {
+ "kind" : "INPUT_REF",
+ "inputIndex" : 1,
+ "type" : "TIMESTAMP(3)"
+ },
+ "rowtimeFieldIndex" : 1,
+ "inputProperties" : [ {
+ "requiredDistribution" : {
+ "type" : "UNKNOWN"
+ },
+ "damBehavior" : "PIPELINED",
+ "priority" : 0
+ } ],
+ "outputType" : {
+ "type" : "ROW",
+ "fields" : [ {
+ "name" : "a",
+ "fieldType" : "INT"
+ }, {
+ "name" : "rowtime",
+ "fieldType" : {
+ "type" : "TIMESTAMP_WITHOUT_TIME_ZONE",
+ "precision" : 3,
+ "kind" : "ROWTIME"
+ }
+ } ]
+ },
+ "description" : "WatermarkAssigner(rowtime=[rowtime], watermark=[rowtime])"
+ }, {
+ "id" : 3,
+ "type" : "stream-exec-exchange_1",
+ "inputProperties" : [ {
+ "requiredDistribution" : {
+ "type" : "HASH",
+ "keys" : [ 0 ]
+ },
+ "damBehavior" : "PIPELINED",
+ "priority" : 0
+ } ],
+ "outputType" : {
+ "type" : "ROW",
+ "fields" : [ {
+ "name" : "a",
+ "fieldType" : "INT"
+ }, {
+ "name" : "rowtime",
+ "fieldType" : {
+ "type" : "TIMESTAMP_WITHOUT_TIME_ZONE",
+ "precision" : 3,
+ "kind" : "ROWTIME"
+ }
+ } ]
+ },
+ "description" : "Exchange(distribution=[hash[a]])"
+ }, {
+ "id" : 4,
+ "type" : "stream-exec-table-source-scan_2",
+ "scanTableSource" : {
+ "table" : {
+ "identifier" : "`default_catalog`.`default_database`.`MyTable2`",
+ "resolvedTable" : {
+ "schema" : {
+ "columns" : [ {
+ "name" : "a",
+ "dataType" : "INT"
+ }, {
+ "name" : "b",
+ "dataType" : "VARCHAR(2147483647)"
+ }, {
+ "name" : "c",
+ "dataType" : "BIGINT"
+ }, {
+ "name" : "proctime",
+ "kind" : "COMPUTED",
+ "expression" : {
+ "rexNode" : {
+ "kind" : "CALL",
+ "internalName" : "$PROCTIME$1",
+ "type" : {
+ "type" : "TIMESTAMP_WITH_LOCAL_TIME_ZONE",
+ "nullable" : false,
+ "precision" : 3,
+ "kind" : "PROCTIME"
+ }
+ },
+ "serializableString" : "PROCTIME()"
+ }
+ }, {
+ "name" : "rowtime",
+ "dataType" : {
+ "type" : "TIMESTAMP_WITHOUT_TIME_ZONE",
+ "precision" : 3,
+ "kind" : "ROWTIME"
+ }
+ } ],
+ "watermarkSpecs" : [ {
+ "rowtimeAttribute" : "rowtime",
+ "expression" : {
+ "rexNode" : {
+ "kind" : "INPUT_REF",
+ "inputIndex" : 4,
+ "type" : "TIMESTAMP(3)"
+ },
+ "serializableString" : "`rowtime`"
+ }
+ } ]
+ },
+ "options" : {
+ "bounded" : "false",
+ "connector" : "values"
+ }
+ }
+ },
+ "abilities" : [ {
+ "type" : "ProjectPushDown",
+ "projectedFields" : [ [ 0 ], [ 1 ], [ 3 ] ],
+ "producedType" : "ROW<`a` INT, `b` VARCHAR(2147483647), `rowtime`
TIMESTAMP(3)> NOT NULL"
+ }, {
+ "type" : "ReadingMetadata",
+ "metadataKeys" : [ ],
+ "producedType" : "ROW<`a` INT, `b` VARCHAR(2147483647), `rowtime`
TIMESTAMP(3)> NOT NULL"
+ } ]
+ },
+ "outputType" : "ROW<`a` INT, `b` VARCHAR(2147483647), `rowtime`
TIMESTAMP(3)>",
+ "description" : "TableSourceScan(table=[[default_catalog,
default_database, MyTable2, project=[a, b, rowtime], metadata=[]]], fields=[a,
b, rowtime])"
+ }, {
+ "id" : 5,
+ "type" : "stream-exec-watermark-assigner_1",
+ "watermarkExpr" : {
+ "kind" : "INPUT_REF",
+ "inputIndex" : 2,
+ "type" : "TIMESTAMP(3)"
+ },
+ "rowtimeFieldIndex" : 2,
+ "inputProperties" : [ {
+ "requiredDistribution" : {
+ "type" : "UNKNOWN"
+ },
+ "damBehavior" : "PIPELINED",
+ "priority" : 0
+ } ],
+ "outputType" : {
+ "type" : "ROW",
+ "fields" : [ {
+ "name" : "a",
+ "fieldType" : "INT"
+ }, {
+ "name" : "b",
+ "fieldType" : "VARCHAR(2147483647)"
+ }, {
+ "name" : "rowtime",
+ "fieldType" : {
+ "type" : "TIMESTAMP_WITHOUT_TIME_ZONE",
+ "precision" : 3,
+ "kind" : "ROWTIME"
+ }
+ } ]
+ },
+ "description" : "WatermarkAssigner(rowtime=[rowtime], watermark=[rowtime])"
+ }, {
+ "id" : 6,
+ "type" : "stream-exec-exchange_1",
+ "inputProperties" : [ {
+ "requiredDistribution" : {
+ "type" : "HASH",
+ "keys" : [ 0 ]
+ },
+ "damBehavior" : "PIPELINED",
+ "priority" : 0
+ } ],
+ "outputType" : {
+ "type" : "ROW",
+ "fields" : [ {
+ "name" : "a",
+ "fieldType" : "INT"
+ }, {
+ "name" : "b",
+ "fieldType" : "VARCHAR(2147483647)"
+ }, {
+ "name" : "rowtime",
+ "fieldType" : {
+ "type" : "TIMESTAMP_WITHOUT_TIME_ZONE",
+ "precision" : 3,
+ "kind" : "ROWTIME"
+ }
+ } ]
+ },
+ "description" : "Exchange(distribution=[hash[a]])"
+ }, {
+ "id" : 7,
+ "type" : "stream-exec-interval-join_1",
+ "intervalJoinSpec" : {
+ "joinSpec" : {
+ "joinType" : "LEFT",
+ "leftKeys" : [ 0 ],
+ "rightKeys" : [ 0 ],
+ "filterNulls" : [ true ],
+ "nonEquiCondition" : null
+ },
+ "windowBounds" : {
+ "isEventTime" : true,
+ "leftLowerBound" : -10000,
+ "leftUpperBound" : 3600000,
+ "leftTimeIndex" : 1,
+ "rightTimeIndex" : 2
+ }
+ },
+ "earlyFireDelay" : 5000,
+ "earlyFireTimeMode" : "ROWTIME",
+ "inputProperties" : [ {
+ "requiredDistribution" : {
+ "type" : "UNKNOWN"
+ },
+ "damBehavior" : "PIPELINED",
+ "priority" : 0
+ }, {
+ "requiredDistribution" : {
+ "type" : "UNKNOWN"
+ },
+ "damBehavior" : "PIPELINED",
+ "priority" : 0
+ } ],
+ "outputType" : {
+ "type" : "ROW",
+ "fields" : [ {
+ "name" : "a",
+ "fieldType" : "INT"
+ }, {
+ "name" : "rowtime",
+ "fieldType" : {
+ "type" : "TIMESTAMP_WITHOUT_TIME_ZONE",
+ "precision" : 3,
+ "kind" : "ROWTIME"
+ }
+ }, {
+ "name" : "a0",
+ "fieldType" : "INT"
+ }, {
+ "name" : "b",
+ "fieldType" : "VARCHAR(2147483647)"
+ }, {
+ "name" : "rowtime0",
+ "fieldType" : {
+ "type" : "TIMESTAMP_WITHOUT_TIME_ZONE",
+ "precision" : 3,
+ "kind" : "ROWTIME"
+ }
+ } ]
+ },
+ "description" : "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], earlyFireDelay=[5000],
earlyFireTimeMode=[ROWTIME])"
+ }, {
+ "id" : 8,
+ "type" : "stream-exec-calc_1",
+ "projection" : [ {
+ "kind" : "INPUT_REF",
+ "inputIndex" : 0,
+ "type" : "INT"
+ }, {
+ "kind" : "INPUT_REF",
+ "inputIndex" : 3,
+ "type" : "VARCHAR(2147483647)"
+ } ],
+ "condition" : null,
+ "inputProperties" : [ {
+ "requiredDistribution" : {
+ "type" : "UNKNOWN"
+ },
+ "damBehavior" : "PIPELINED",
+ "priority" : 0
+ } ],
+ "outputType" : "ROW<`a` INT, `b` VARCHAR(2147483647)>",
+ "description" : "Calc(select=[a, b])"
+ }, {
+ "id" : 9,
+ "type" : "stream-exec-sink_2",
+ "configuration" : {
+ "table.exec.sink.keyed-shuffle" : "AUTO",
+ "table.exec.sink.not-null-enforcer" : "ERROR",
+ "table.exec.sink.rowtime-inserter" : "ENABLED",
+ "table.exec.sink.type-length-enforcer" : "IGNORE",
+ "table.exec.sink.upsert-materialize" : "AUTO"
+ },
+ "dynamicTableSink" : {
+ "table" : {
+ "identifier" : "`default_catalog`.`default_database`.`MySink`",
+ "resolvedTable" : {
+ "schema" : {
+ "columns" : [ {
+ "name" : "a",
+ "dataType" : "INT"
+ }, {
+ "name" : "b",
+ "dataType" : "VARCHAR(2147483647)"
+ } ]
+ },
+ "options" : {
+ "connector" : "values"
+ }
+ }
+ }
+ },
+ "inputChangelogMode" : [ "INSERT" ],
+ "inputProperties" : [ {
+ "requiredDistribution" : {
+ "type" : "UNKNOWN"
+ },
+ "damBehavior" : "PIPELINED",
+ "priority" : 0
+ } ],
+ "outputType" : "ROW<`a` INT, `b` VARCHAR(2147483647)>",
+ "description" : "Sink(table=[default_catalog.default_database.MySink],
fields=[a, b])"
+ } ],
+ "edges" : [ {
+ "source" : 1,
+ "target" : 2,
+ "shuffle" : {
+ "type" : "FORWARD"
+ },
+ "shuffleMode" : "PIPELINED"
+ }, {
+ "source" : 2,
+ "target" : 3,
+ "shuffle" : {
+ "type" : "FORWARD"
+ },
+ "shuffleMode" : "PIPELINED"
+ }, {
+ "source" : 4,
+ "target" : 5,
+ "shuffle" : {
+ "type" : "FORWARD"
+ },
+ "shuffleMode" : "PIPELINED"
+ }, {
+ "source" : 5,
+ "target" : 6,
+ "shuffle" : {
+ "type" : "FORWARD"
+ },
+ "shuffleMode" : "PIPELINED"
+ }, {
+ "source" : 3,
+ "target" : 7,
+ "shuffle" : {
+ "type" : "FORWARD"
+ },
+ "shuffleMode" : "PIPELINED"
+ }, {
+ "source" : 6,
+ "target" : 7,
+ "shuffle" : {
+ "type" : "FORWARD"
+ },
+ "shuffleMode" : "PIPELINED"
+ }, {
+ "source" : 7,
+ "target" : 8,
+ "shuffle" : {
+ "type" : "FORWARD"
+ },
+ "shuffleMode" : "PIPELINED"
+ }, {
+ "source" : 8,
+ "target" : 9,
+ "shuffle" : {
+ "type" : "FORWARD"
+ },
+ "shuffleMode" : "PIPELINED"
+ } ]
+}
\ No newline at end of file