This is an automated email from the ASF dual-hosted git repository. zstan pushed a commit to branch calcite-trace in repository https://gitbox.apache.org/repos/asf/ignite.git
commit 200901e43247e1b7b91ed8c785c730608a697313 Author: zstan <[email protected]> AuthorDate: Wed Sep 23 13:04:32 2026 +0300 rule fix --- .../rule/AbstractIgniteJoinConverterRule.java | 69 ++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/AbstractIgniteJoinConverterRule.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/AbstractIgniteJoinConverterRule.java index 753e3750b29..f91214237b7 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/AbstractIgniteJoinConverterRule.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/AbstractIgniteJoinConverterRule.java @@ -23,11 +23,13 @@ import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.calcite.plan.RelOptRuleCall; +import org.apache.calcite.plan.RelTraitSet; import org.apache.calcite.plan.volcano.RelSubset; import org.apache.calcite.rel.RelNode; import org.apache.calcite.rel.core.Join; @@ -36,6 +38,8 @@ import org.apache.calcite.rel.hint.RelHint; import org.apache.calcite.rel.logical.LogicalJoin; import org.apache.ignite.internal.processors.query.calcite.hint.HintDefinition; import org.apache.ignite.internal.processors.query.calcite.hint.HintUtils; +import org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention; +import org.apache.ignite.internal.processors.query.calcite.trait.TraitsAwareIgniteRel; import org.apache.ignite.internal.util.typedef.F; import static org.apache.calcite.util.Util.last; @@ -86,6 +90,71 @@ abstract class AbstractIgniteJoinConverterRule extends AbstractIgniteConverterRu return super.matches(call) && matchesJoin(call) && !disabledByHints(call.rel(0)); } + /** {@inheritDoc} */ + @Override public void onMatch(RelOptRuleCall call) { + LogicalJoin join = call.rel(0); + + if (!join.getTraitSet().contains(getInTrait())) + return; + + RelNode converted = convert(join); + + if (converted == null) + return; + + call.transformTo(converted); + + emitDeliveredVariants(call, converted); + } + + /** + * Registers the variants of the converted join that can be derived from the traits (distribution, collation, + * rewindability, correlation) its inputs have already delivered. + * <p> + * The top-down driver derives such variants itself ({@code DeriveTrait} task), but only for physical nodes that + * exist when the subset of the join with default traits is optimized. Several converter rules match the same + * {@link LogicalJoin}, they fire one after another, and every join implementation produced after the subset has + * been optimized never gets its {@code DeriveTrait} task: the driver only passes the required traits of the subsets + * being optimized at that moment (usually {@code single}) through it. As a result an equi-join on affinity keys + * gets its colocated (affinity distributed) variant only if its converter rule happens to fire first. + * <p> + * To make the join implementations independent of the rule order the converter derives the variants on its own. + * If an input has nothing delivered yet the derivation is skipped, in that case the driver derives the variants later. + * + * @param call Rule call. + * @param converted Converted join with default traits. + */ + private static void emitDeliveredVariants(RelOptRuleCall call, RelNode converted) { + if (!(converted instanceof TraitsAwareIgniteRel)) + return; + + // Default trait set is not interesting, DeriveTrait skips it too. + RelTraitSet dfltTraits = converted.getCluster().traitSet(); + + List<List<RelTraitSet>> inTraits = new ArrayList<>(converted.getInputs().size()); + + for (RelNode input : converted.getInputs()) { + if (!(input instanceof RelSubset)) + return; + + List<RelTraitSet> delivered = ((RelSubset)input).getRelList().stream() + .filter(rel -> rel.getConvention() == IgniteConvention.INSTANCE) + .map(RelNode::getTraitSet) + .filter(traits -> !traits.equalsSansConvention(dfltTraits)) + .distinct() + .collect(Collectors.toList()); + + // Input is not optimized yet, the driver derives the variants after it is. + if (delivered.isEmpty()) + return; + + inTraits.add(delivered); + } + + for (RelNode variant : ((TraitsAwareIgniteRel)converted).derive(inTraits)) + call.transformTo(variant); + } + /** */ private boolean disabledByHints(LogicalJoin join) { Collection<TableScan> joinTables = joinTables(join);
