|
19 | 19 | package org.apache.flink.table.planner.plan.rules.logical;
|
20 | 20 |
|
21 | 21 | import org.apache.flink.table.api.TableException;
|
22 |
| -import org.apache.flink.table.planner.calcite.FlinkTypeFactory; |
23 |
| -import org.apache.flink.table.planner.plan.nodes.logical.FlinkLogicalSnapshot; |
| 22 | +import org.apache.flink.table.planner.plan.nodes.logical.FlinkLogicalJoin; |
24 | 23 | import org.apache.flink.table.planner.plan.nodes.physical.stream.StreamPhysicalMultiJoin;
|
25 |
| -import org.apache.flink.table.planner.plan.utils.JoinUtil; |
| 24 | +import org.apache.flink.table.planner.plan.utils.IntervalJoinUtil; |
26 | 25 |
|
27 | 26 | import org.apache.calcite.plan.RelOptRuleCall;
|
28 | 27 | import org.apache.calcite.plan.RelOptUtil;
|
29 | 28 | import org.apache.calcite.plan.RelRule;
|
| 29 | +import org.apache.calcite.plan.hep.HepRelVertex; |
| 30 | +import org.apache.calcite.plan.volcano.RelSubset; |
30 | 31 | import org.apache.calcite.rel.RelNode;
|
| 32 | +import org.apache.calcite.rel.SingleRel; |
31 | 33 | import org.apache.calcite.rel.core.Join;
|
32 | 34 | import org.apache.calcite.rel.core.JoinInfo;
|
33 | 35 | import org.apache.calcite.rel.core.JoinRelType;
|
34 | 36 | import org.apache.calcite.rel.logical.LogicalJoin;
|
| 37 | +import org.apache.calcite.rel.logical.LogicalSnapshot; |
35 | 38 | import org.apache.calcite.rel.rules.CoreRules;
|
36 | 39 | import org.apache.calcite.rel.rules.FilterMultiJoinMergeRule;
|
37 | 40 | import org.apache.calcite.rel.rules.MultiJoin;
|
38 | 41 | import org.apache.calcite.rel.rules.ProjectMultiJoinMergeRule;
|
39 | 42 | import org.apache.calcite.rel.rules.TransformationRule;
|
40 |
| -import org.apache.calcite.rel.type.RelDataType; |
41 | 43 | import org.apache.calcite.rel.type.RelDataTypeField;
|
42 | 44 | import org.apache.calcite.rex.RexBuilder;
|
43 | 45 | import org.apache.calcite.rex.RexInputRef;
|
44 | 46 | import org.apache.calcite.rex.RexNode;
|
45 | 47 | import org.apache.calcite.rex.RexUtil;
|
46 | 48 | import org.apache.calcite.rex.RexVisitorImpl;
|
47 |
| -import org.apache.calcite.sql.validate.SqlValidatorUtil; |
48 | 49 | import org.apache.calcite.tools.RelBuilderFactory;
|
49 | 50 | import org.apache.calcite.util.ImmutableBitSet;
|
50 | 51 | import org.apache.calcite.util.ImmutableIntList;
|
@@ -140,25 +141,21 @@ public JoinToMultiJoinRule(
|
140 | 141 |
|
141 | 142 | // ~ Methods ----------------------------------------------------------------
|
142 | 143 |
|
| 144 | + /** |
| 145 | + * This rule matches only INNER and LEFT joins. Right joins are expected to be rewritten to left |
| 146 | + * joins by the optimizer with {@link FlinkRightJoinToLeftJoinRule} |
| 147 | + */ |
143 | 148 | @Override
|
144 | 149 | public boolean matches(RelOptRuleCall call) {
|
145 | 150 | final Join origJoin = call.rel(0);
|
146 | 151 | if (origJoin.getJoinType() != JoinRelType.INNER
|
147 | 152 | && origJoin.getJoinType() != JoinRelType.LEFT) {
|
148 |
| - /* This rule expects only INNER and LEFT joins. Right joins are expected to be |
149 |
| - rewritten to left joins by the optimizer with {@link FlinkRightJoinToLeftJoinRule} */ |
150 |
| - return false; |
151 |
| - } |
152 |
| - final RelNode left = call.rel(1); |
153 |
| - final RelNode right = call.rel(2); |
154 |
| - |
155 |
| - // Check for temporal/lookup joins (FOR SYSTEM_TIME AS OF) - these should not be merged |
156 |
| - if (containsSnapshot(left) || containsSnapshot(right)) { |
157 | 153 | return false;
|
158 | 154 | }
|
159 | 155 |
|
160 |
| - // Check for interval joins - these should not be merged as they have special time semantics |
161 |
| - if (hasTimeAttributes(origJoin, left, right)) { |
| 156 | + // Check for interval joins and temporal join - these should not be merged |
| 157 | + // as they have special time semantics |
| 158 | + if (isIntervalJoin(origJoin) || isTemporalJoin(call)) { |
162 | 159 | return false;
|
163 | 160 | }
|
164 | 161 |
|
@@ -663,66 +660,62 @@ private List<RexNode> combinePostJoinFilters(Join joinRel, RelNode left, RelNode
|
663 | 660 | }
|
664 | 661 |
|
665 | 662 | /**
|
666 |
| - * Checks if a RelNode tree contains FlinkLogicalSnapshot nodes, which indicate temporal/lookup |
667 |
| - * joins. These joins have special semantics and should not be merged into MultiJoin. |
| 663 | + * Checks if a join is an interval join. Interval joins have special time-based semantics and |
| 664 | + * should not be merged into MultiJoin. |
668 | 665 | *
|
669 |
| - * @param node the RelNode to check |
670 |
| - * @return true if the node or its children contain FlinkLogicalSnapshot |
| 666 | + * @param join the join to check |
| 667 | + * @return true if the join condition or outputs access time attributes |
671 | 668 | */
|
672 |
| - private boolean containsSnapshot(RelNode node) { |
673 |
| - if (node instanceof FlinkLogicalSnapshot) { |
| 669 | + private boolean isIntervalJoin(Join join) { |
| 670 | + if (!(join instanceof LogicalJoin)) { |
674 | 671 | return true;
|
675 | 672 | }
|
676 | 673 |
|
677 |
| - // Check if any input contains a snapshot |
678 |
| - for (RelNode input : node.getInputs()) { |
679 |
| - if (containsSnapshot(input)) { |
680 |
| - return true; |
681 |
| - } |
682 |
| - } |
683 |
| - |
684 |
| - return false; |
| 674 | + FlinkLogicalJoin flinkLogicalJoin = |
| 675 | + (FlinkLogicalJoin) FlinkLogicalJoin.CONVERTER().convert(join); |
| 676 | + return IntervalJoinUtil.satisfyIntervalJoin(flinkLogicalJoin); |
685 | 677 | }
|
686 | 678 |
|
687 | 679 | /**
|
688 |
| - * Checks if a join accesses time attributes, which indicates an interval join. Interval joins |
689 |
| - * have special time-based semantics and should not be merged into MultiJoin. |
| 680 | + * Checks if a join is a temporal/lookup join. Interval joins have special time-based semantics |
| 681 | + * (FOR SYSTEM_TIME AS OF) and should not be merged into a MultiJoin. |
690 | 682 | *
|
691 |
| - * @param join the join to check |
692 |
| - * @param left the left input |
693 |
| - * @param right the right input |
| 683 | + * @param call the join call to check |
694 | 684 | * @return true if the join condition or outputs access time attributes
|
695 | 685 | */
|
696 |
| - private boolean hasTimeAttributes(Join join, RelNode left, RelNode right) { |
697 |
| - // Time attributes must not be in the output type of regular join |
698 |
| - final boolean timeAttrInOutput = |
699 |
| - join.getRowType().getFieldList().stream() |
700 |
| - .anyMatch(f -> FlinkTypeFactory.isTimeIndicatorType(f.getType())); |
701 |
| - if (timeAttrInOutput) { |
702 |
| - return true; |
703 |
| - } |
| 686 | + private boolean isTemporalJoin(RelOptRuleCall call) { |
| 687 | + final RelNode left = call.rel(1); |
| 688 | + final RelNode right = call.rel(2); |
704 | 689 |
|
705 |
| - // Join condition must not access time attributes |
706 |
| - if (join.getCondition() != null) { |
707 |
| - final RelDataType inputsRowType = |
708 |
| - createInputsRowType(left, right, join.getCluster().getTypeFactory()); |
709 |
| - return JoinUtil.accessesTimeAttribute(join.getCondition(), inputsRowType); |
| 690 | + if (containsSnapshot(left) || containsSnapshot(right)) { |
| 691 | + return true; |
710 | 692 | }
|
711 |
| - |
712 | 693 | return false;
|
713 | 694 | }
|
714 | 695 |
|
715 |
| - /** Creates a combined row type from the left and right inputs for time attribute checking. */ |
716 |
| - private RelDataType createInputsRowType( |
717 |
| - RelNode left, |
718 |
| - RelNode right, |
719 |
| - org.apache.calcite.rel.type.RelDataTypeFactory typeFactory) { |
720 |
| - return SqlValidatorUtil.createJoinType( |
721 |
| - typeFactory, |
722 |
| - left.getRowType(), |
723 |
| - right.getRowType(), |
724 |
| - null, |
725 |
| - java.util.Collections.emptyList()); |
| 696 | + /** |
| 697 | + * Checks if a RelNode tree contains FlinkLogicalSnapshot nodes, which indicate temporal/lookup |
| 698 | + * joins. These joins have special semantics and should not be merged into MultiJoin. |
| 699 | + * |
| 700 | + * @param relNode the RelNode to check |
| 701 | + * @return true if the node or its children contain FlinkLogicalSnapshot |
| 702 | + */ |
| 703 | + private boolean containsSnapshot(RelNode relNode) { |
| 704 | + RelNode original = null; |
| 705 | + if (relNode instanceof RelSubset) { |
| 706 | + original = ((RelSubset) relNode).getOriginal(); |
| 707 | + } else if (relNode instanceof HepRelVertex) { |
| 708 | + original = ((HepRelVertex) relNode).getCurrentRel(); |
| 709 | + } else { |
| 710 | + original = relNode; |
| 711 | + } |
| 712 | + if (original instanceof LogicalSnapshot) { |
| 713 | + return true; |
| 714 | + } else if (original instanceof SingleRel) { |
| 715 | + return containsSnapshot(((SingleRel) original).getInput()); |
| 716 | + } else { |
| 717 | + return false; |
| 718 | + } |
726 | 719 | }
|
727 | 720 |
|
728 | 721 | // ~ Inner Classes ----------------------------------------------------------
|
|
0 commit comments