Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IGNITE-23799 : Calcite. Hash join. #11770

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions docs/_docs/SQL/sql-calcite.adoc
Original file line number Diff line number Diff line change
@@ -371,10 +371,10 @@ SELECT /*+ ENFORCE_JOIN_ORDER */ T1.V1, T2.V1, T2.V2, T3.V1, T3.V2, T3.V3 FROM T
SELECT t1.v1, t3.v2 FROM TBL1 t1 JOIN TBL3 t3 on t1.v3=t3.v3 WHERE t1.v2 in (SELECT /*+ ENFORCE_JOIN_ORDER */ t2.v2 FROM TBL2 t2 JOIN TBL3 t3 ON t2.v1=t3.v1)
----

==== MERGE_JOIN, NL_JOIN, CNL_JOIN
Forces certain join type: Merge, Nested Loop and Correlated Nested Loop respectively.
==== MERGE_JOIN, NL_JOIN, CNL_JOIN, HASH_JOIN
Forces certain join type: Merge, Nested Loop, Correlated Nested Loop and Hash Join respectively.

Every of those has the negation like 'NO_INDEX': CNL_JOIN, NO_CNL_JOIN. The negation hint disables certain join type.
Every of those has the negation like 'NO_INDEX': CNL_JOIN, NO_CNL_JOIN, NO_HASH_JOIN. The negation hint disables certain join type.

===== Parameters:
* Empty. To force or disable certain join type for every join.
@@ -389,7 +389,7 @@ SELECT /*+ NL_JOIN(TBL3,TBL1) */ t4.v1, t2.v2 FROM TBL1 t4 JOIN TBL2 t2 on t1.v3
SELECT t1.v1, t2.v2 FROM TBL2 t1 JOIN TBL1 t2 on t1.v3=t2.v3 WHERE t2.v3 in (SELECT /*+ NO_CNL_JOIN(TBL4) */ t3.v3 FROM TBL3 t3 JOIN TBL4 t4 on t3.v1=t4.v1)
SELECT t4.v1, t2.v2 FROM TBL1 t4 JOIN TBL2 t2 on t1.v3=t2.v3 WHERE t2.v1 in (SELECT t3.v3 FROM TBL3 t3 JOIN TBL1 /*+ NL_JOIN */ t4 on t3.v2=t4.v2)
SELECT t4.v1, t2.v2 FROM TBL1 t4 JOIN TBL2 t2 on t1.v3=t2.v3 WHERE t2.v1 in (SELECT t3.v3 FROM TBL3 t3 JOIN TBL1 /*+ HASH_JOIN */ t4 on t3.v2=t4.v2)
----

==== EXPAND_DISTINCT_AGG
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@
package org.apache.ignite.internal.processors.query.calcite.exec;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
@@ -49,6 +50,7 @@
import org.apache.ignite.internal.processors.query.calcite.exec.rel.CorrelatedNestedLoopJoinNode;
import org.apache.ignite.internal.processors.query.calcite.exec.rel.FilterNode;
import org.apache.ignite.internal.processors.query.calcite.exec.rel.HashAggregateNode;
import org.apache.ignite.internal.processors.query.calcite.exec.rel.HashJoinNode;
import org.apache.ignite.internal.processors.query.calcite.exec.rel.Inbox;
import org.apache.ignite.internal.processors.query.calcite.exec.rel.IndexSpoolNode;
import org.apache.ignite.internal.processors.query.calcite.exec.rel.IntersectNode;
@@ -74,6 +76,7 @@
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteHashIndexSpool;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteHashJoin;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexBound;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexCount;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
@@ -266,6 +269,16 @@ public LogicalRelImplementor(
return node;
}

/** {@inheritDoc} */
@Override public Node<Row> visit(IgniteHashJoin rel) {
Node<Row> node = HashJoinNode.create(ctx, rel.getRowType(), rel.getLeft().getRowType(), rel.getRight().getRowType(),
rel.getJoinType(), rel.analyzeCondition());

node.register(Arrays.asList(visit(rel.getLeft()), visit(rel.getRight())));

return node;
}

/** {@inheritDoc} */
@Override public Node<Row> visit(IgniteCorrelatedNestedLoopJoin rel) {
RelDataType outType = rel.getRowType();
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*
* 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.ignite.internal.processors.query.calcite.exec.rel;

import java.util.ArrayDeque;
import java.util.Deque;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
import org.apache.ignite.internal.util.typedef.F;
import org.jetbrains.annotations.Nullable;

/** Right-part materialized join node. Holds data from the right part locally. */
public abstract class AbstractRightMaterializedJoinNode<Row> extends MemoryTrackingNode<Row> {
/** Special flag which marks that all the rows are received. */
protected static final int NOT_WAITING = -1;

/** */
protected final Deque<Row> leftInBuf = new ArrayDeque<>(IN_BUFFER_SIZE);

/** */
protected boolean inLoop;

/** */
protected int requested;

/** */
protected int waitingLeft;

/** */
protected int waitingRight;

/** */
protected @Nullable Row left;

/** */
protected AbstractRightMaterializedJoinNode(ExecutionContext<Row> ctx, RelDataType rowType) {
super(ctx, rowType);
}

/** */
protected abstract void join() throws Exception;

/** */
protected abstract void pushRight(Row row) throws Exception;

/** {@inheritDoc} */
@Override public void request(int rowsCnt) throws Exception {
assert !F.isEmpty(sources()) && sources().size() == 2;
assert rowsCnt > 0 && requested == 0;

checkState();

requested = rowsCnt;

if (!inLoop)
context().execute(this::doJoin, this::onError);
}

/** {@inheritDoc} */
@Override protected void rewindInternal() {
requested = 0;
waitingLeft = 0;
waitingRight = 0;
left = null;

leftInBuf.clear();
}

/** {@inheritDoc} */
@Override protected Downstream<Row> requestDownstream(int idx) {
if (idx == 0) {
return new Downstream<>() {
@Override public void push(Row row) throws Exception {
pushLeft(row);
}

@Override public void end() throws Exception {
endLeft();
}

@Override public void onError(Throwable e) {
AbstractRightMaterializedJoinNode.this.onError(e);
}
};
}
else if (idx == 1) {
return new Downstream<>() {
@Override public void push(Row row) throws Exception {
pushRight(row);
}

@Override public void end() throws Exception {
endRight();
}

@Override public void onError(Throwable e) {
AbstractRightMaterializedJoinNode.this.onError(e);
}
};
}

throw new IndexOutOfBoundsException();
}

/** */
private void pushLeft(Row row) throws Exception {
assert downstream() != null;
assert waitingLeft > 0;

checkState();

--waitingLeft;

leftInBuf.add(row);

join();
}

/** */
private void endLeft() throws Exception {
assert downstream() != null;
assert waitingLeft > 0;

checkState();

waitingLeft = NOT_WAITING;

join();
}

/** */
private void endRight() throws Exception {
assert downstream() != null;
assert waitingRight > 0;

checkState();

waitingRight = NOT_WAITING;

join();
}

/** */
protected Node<Row> leftSource() {
return sources().get(0);
}

/** */
protected Node<Row> rightSource() {
return sources().get(1);
}

/** */
private void doJoin() throws Exception {
checkState();

join();
}
}

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -173,6 +173,32 @@ public enum HintDefinition {
@Override public HintOptionsChecker optionsChecker() {
return CNL_JOIN.optionsChecker();
}
},

/** Forces hash join. */
HASH_JOIN {
/** {@inheritDoc} */
@Override public HintPredicate predicate() {
return joinHintPredicate();
}

/** {@inheritDoc} */
@Override public HintOptionsChecker optionsChecker() {
return HintsConfig.OPTS_CHECK_NO_KV;
}
},

/** Disables hash join. */
NO_HASH_JOIN {
/** {@inheritDoc} */
@Override public HintPredicate predicate() {
return HASH_JOIN.predicate();
}

/** {@inheritDoc} */
@Override public HintOptionsChecker optionsChecker() {
return HASH_JOIN.optionsChecker();
}
};

/**
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteHashIndexSpool;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteHashJoin;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexBound;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexCount;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
@@ -144,6 +145,12 @@ private IgniteReceiver collect(IgniteReceiver receiver) {
visit((IgniteRel)rel.getRight())));
}

/** {@inheritDoc} */
@Override public IgniteRel visit(IgniteHashJoin rel) {
return rel.clone(cluster, F.asList(visit((IgniteRel)rel.getLeft()),
visit((IgniteRel)rel.getRight())));
}

/** {@inheritDoc} */
@Override public IgniteRel visit(IgniteMergeJoin rel) {
return rel.clone(cluster, F.asList(visit((IgniteRel)rel.getLeft()),
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteHashIndexSpool;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteHashJoin;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexBound;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexCount;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
@@ -79,6 +80,11 @@ public class IgniteRelShuttle implements IgniteRelVisitor<IgniteRel> {
return processNode(rel);
}

/** {@inheritDoc} */
@Override public IgniteRel visit(IgniteHashJoin rel) {
return processNode(rel);
}

/** {@inheritDoc} */
@Override public IgniteRel visit(IgniteCorrelatedNestedLoopJoin rel) {
return processNode(rel);
Original file line number Diff line number Diff line change
@@ -49,6 +49,7 @@
import org.apache.ignite.internal.processors.query.calcite.rule.FilterSpoolMergeToHashIndexSpoolRule;
import org.apache.ignite.internal.processors.query.calcite.rule.FilterSpoolMergeToSortedIndexSpoolRule;
import org.apache.ignite.internal.processors.query.calcite.rule.HashAggregateConverterRule;
import org.apache.ignite.internal.processors.query.calcite.rule.HashJoinConverterRule;
import org.apache.ignite.internal.processors.query.calcite.rule.IndexCountRule;
import org.apache.ignite.internal.processors.query.calcite.rule.IndexMinMaxRule;
import org.apache.ignite.internal.processors.query.calcite.rule.LogicalScanConverterRule;
@@ -238,6 +239,7 @@ public enum PlannerPhase {
CorrelatedNestedLoopJoinRule.INSTANCE,
CorrelateToNestedLoopRule.INSTANCE,
NestedLoopJoinConverterRule.INSTANCE,
HashJoinConverterRule.INSTANCE,

ValuesConverterRule.INSTANCE,
LogicalScanConverterRule.INDEX_SCAN,
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* 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.ignite.internal.processors.query.calcite.rel;

import java.util.List;
import java.util.Set;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptCost;
import org.apache.calcite.plan.RelOptPlanner;
import org.apache.calcite.plan.RelTraitSet;
import org.apache.calcite.rel.RelInput;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.CorrelationId;
import org.apache.calcite.rel.core.Join;
import org.apache.calcite.rel.core.JoinRelType;
import org.apache.calcite.rel.metadata.RelMetadataQuery;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.util.ImmutableBitSet;
import org.apache.calcite.util.Util;
import org.apache.ignite.internal.processors.query.calcite.metadata.cost.IgniteCost;
import org.apache.ignite.internal.processors.query.calcite.metadata.cost.IgniteCostFactory;
import org.apache.ignite.internal.processors.query.calcite.util.Commons;

/** Represent hash join. */
public class IgniteHashJoin extends AbstractIgniteJoin {
/** */
public IgniteHashJoin(
RelOptCluster cluster,
RelTraitSet traitSet,
RelNode left,
RelNode right,
RexNode condition,
Set<CorrelationId> variablesSet,
JoinRelType joinType
) {
super(cluster, traitSet, left, right, condition, variablesSet, joinType);
}

/** Constructor. */
public IgniteHashJoin(RelInput input) {
this(
input.getCluster(),
input.getTraitSet().replace(IgniteConvention.INSTANCE),
input.getInputs().get(0),
input.getInputs().get(1),
input.getExpression("condition"),
Set.copyOf(Commons.transform(input.getIntegerList("variablesSet"), CorrelationId::new)),
input.getEnum("joinType", JoinRelType.class)
);
}

/** {@inheritDoc} */
@Override public RelOptCost computeSelfCost(RelOptPlanner planner, RelMetadataQuery mq) {
IgniteCostFactory costFactory = (IgniteCostFactory)planner.getCostFactory();

double leftRowCnt = mq.getRowCount(getLeft());

double rightRowCnt = mq.getRowCount(getRight());

if (Double.isInfinite(leftRowCnt) || Double.isInfinite(rightRowCnt))
return planner.getCostFactory().makeInfiniteCost();

double rowCnt = leftRowCnt + rightRowCnt;

int rightKeysSize = joinInfo.rightKeys.size();

double rightSize = rightRowCnt * IgniteCost.AVERAGE_FIELD_SIZE * getRight().getRowType().getFieldCount();

double distRightRows = Util.first(mq.getDistinctRowCount(right, ImmutableBitSet.of(joinInfo.rightKeys), null), 0.9 * rightRowCnt);

rightSize += distRightRows * rightKeysSize * IgniteCost.AVERAGE_FIELD_SIZE;

return costFactory.makeCost(rowCnt, rowCnt * IgniteCost.HASH_LOOKUP_COST, 0, rightSize, 0);
}

/** {@inheritDoc} */
@Override public Join copy(
RelTraitSet traitSet,
RexNode condition,
RelNode left,
RelNode right,
JoinRelType joinType,
boolean semiJoinDone
) {
return new IgniteHashJoin(getCluster(), traitSet, left, right, condition, variablesSet, joinType);
}

/** {@inheritDoc} */
@Override public <T> T accept(IgniteRelVisitor<T> visitor) {
return visitor.visit(this);
}

/** {@inheritDoc} */
@Override public IgniteRel clone(RelOptCluster cluster, List<IgniteRel> inputs) {
return new IgniteHashJoin(cluster, getTraitSet(), inputs.get(0), inputs.get(1), getCondition(),
getVariablesSet(), getJoinType());
}
}
Original file line number Diff line number Diff line change
@@ -54,6 +54,11 @@ public interface IgniteRelVisitor<T> {
*/
T visit(IgniteNestedLoopJoin rel);

/**
* See {@link IgniteRelVisitor#visit(IgniteRel)}.
*/
T visit(IgniteHashJoin rel);

/**
* See {@link IgniteRelVisitor#visit(IgniteRel)}
*/
Original file line number Diff line number Diff line change
@@ -40,9 +40,11 @@

import static org.apache.calcite.util.Util.last;
import static org.apache.ignite.internal.processors.query.calcite.hint.HintDefinition.CNL_JOIN;
import static org.apache.ignite.internal.processors.query.calcite.hint.HintDefinition.HASH_JOIN;
import static org.apache.ignite.internal.processors.query.calcite.hint.HintDefinition.MERGE_JOIN;
import static org.apache.ignite.internal.processors.query.calcite.hint.HintDefinition.NL_JOIN;
import static org.apache.ignite.internal.processors.query.calcite.hint.HintDefinition.NO_CNL_JOIN;
import static org.apache.ignite.internal.processors.query.calcite.hint.HintDefinition.NO_HASH_JOIN;
import static org.apache.ignite.internal.processors.query.calcite.hint.HintDefinition.NO_MERGE_JOIN;
import static org.apache.ignite.internal.processors.query.calcite.hint.HintDefinition.NO_NL_JOIN;

@@ -64,6 +66,7 @@ abstract class AbstractIgniteJoinConverterRule extends AbstractIgniteConverterRu
HINTS.put(NL_JOIN, NO_NL_JOIN);
HINTS.put(CNL_JOIN, NO_CNL_JOIN);
HINTS.put(MERGE_JOIN, NO_MERGE_JOIN);
HINTS.put(HASH_JOIN, NO_HASH_JOIN);

ALL_HINTS = Stream.concat(HINTS.keySet().stream(), HINTS.values().stream()).toArray(HintDefinition[]::new);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* 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.ignite.internal.processors.query.calcite.rule;

import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptPlanner;
import org.apache.calcite.plan.RelOptRule;
import org.apache.calcite.plan.RelOptRuleCall;
import org.apache.calcite.plan.RelTraitSet;
import org.apache.calcite.rel.PhysicalNode;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.logical.LogicalJoin;
import org.apache.calcite.rel.metadata.RelMetadataQuery;
import org.apache.calcite.rex.RexCall;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexVisitor;
import org.apache.calcite.rex.RexVisitorImpl;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.util.Util;
import org.apache.ignite.internal.processors.query.calcite.hint.HintDefinition;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteHashJoin;
import org.apache.ignite.internal.util.typedef.F;

/** Hash join converter rule. */
public class HashJoinConverterRule extends AbstractIgniteJoinConverterRule {
/** */
public static final RelOptRule INSTANCE = new HashJoinConverterRule();

/** Ctor. */
public HashJoinConverterRule() {
super("HashJoinConverter", HintDefinition.HASH_JOIN);
}

/** {@inheritDoc} */
@Override public boolean matchesJoin(RelOptRuleCall call) {
LogicalJoin join = call.rel(0);

return !F.isEmpty(join.analyzeCondition().pairs()) && join.analyzeCondition().isEqui() && checkConditions(join.getCondition());
}

/** */
private static boolean checkConditions(RexNode node) {
RexVisitor<Void> v = new RexVisitorImpl<>(true) {
@Override public Void visitCall(RexCall call) {
if (call.getOperator().getKind() != SqlKind.EQUALS && call.getOperator().getKind() != SqlKind.AND)
throw Util.FoundOne.NULL;

return super.visitCall(call);
}
};

try {
node.accept(v);

return true;
}
catch (Util.FoundOne e) {
return false;
}
}

/** {@inheritDoc} */
@Override protected PhysicalNode convert(RelOptPlanner planner, RelMetadataQuery mq, LogicalJoin rel) {
RelOptCluster cluster = rel.getCluster();
RelTraitSet outTraits = cluster.traitSetOf(IgniteConvention.INSTANCE);
RelTraitSet leftInTraits = cluster.traitSetOf(IgniteConvention.INSTANCE);
RelTraitSet rightInTraits = cluster.traitSetOf(IgniteConvention.INSTANCE);
RelNode left = convert(rel.getLeft(), leftInTraits);
RelNode right = convert(rel.getRight(), rightInTraits);

return new IgniteHashJoin(cluster, outTraits, left, right, rel.getCondition(), rel.getVariablesSet(), rel.getJoinType());
}
}
Original file line number Diff line number Diff line change
@@ -217,7 +217,8 @@ public void testCountWithJoin() throws Exception {

awaitPartitionMapExchange(true, true, null);

List<String> joinConverters = Arrays.asList("CorrelatedNestedLoopJoin", "MergeJoinConverter", "NestedLoopJoinConverter");
List<String> joinConverters = Arrays.asList("CorrelatedNestedLoopJoin", "MergeJoinConverter", "NestedLoopJoinConverter",
"HashJoinConverter");

// CorrelatedNestedLoopJoin skipped intentionally since it takes too long to finish
// the query with only CNLJ
Original file line number Diff line number Diff line change
@@ -23,8 +23,10 @@
import java.util.UUID;
import com.google.common.collect.ImmutableSet;
import org.apache.calcite.rel.core.CorrelationId;
import org.apache.calcite.rel.core.JoinInfo;
import org.apache.calcite.rel.core.JoinRelType;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.util.ImmutableIntList;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.internal.IgniteInterruptedCheckedException;
import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
@@ -289,10 +291,7 @@ public void testRightJoin() {

assertEquals(4, rows.size());

Assert.assertArrayEquals(new Object[] {0, "Igor", "Core"}, rows.get(0));
Assert.assertArrayEquals(new Object[] {3, "Alexey", "Core"}, rows.get(1));
Assert.assertArrayEquals(new Object[] {1, "Roman", "SQL"}, rows.get(2));
Assert.assertArrayEquals(new Object[] {2, "Ivan", null}, rows.get(3));
checkDepartmentsJoinEmployeesResults(RIGHT, rows);
}

/**
@@ -409,10 +408,7 @@ public void testSemiJoin() {
while (node.hasNext())
rows.add(node.next());

assertEquals(2, rows.size());

Assert.assertArrayEquals(new Object[] {"Core"}, rows.get(0));
Assert.assertArrayEquals(new Object[] {"SQL"}, rows.get(1));
checkDepartmentsJoinEmployeesResults(SEMI, rows);
}

/**
@@ -469,7 +465,7 @@ public void testAntiJoin() {

assertEquals(1, rows.size());

Assert.assertArrayEquals(new Object[] {"QA"}, rows.get(0));
checkDepartmentsJoinEmployeesResults(ANTI, rows);
}

/**
@@ -538,6 +534,119 @@ public void testCorrelatedNestedLoopJoin() {
}
}

/** Tests 'Select e.id, e.name, d.name as dep_name from DEP d <JOIN TYPE> join EMP e on e.DEPNO = d.DEPNO'. */
@Test
public void testHashJoin() {
for (JoinRelType joinType : F.asList(LEFT, INNER, RIGHT, FULL, SEMI, ANTI)) {
ExecutionContext<Object[]> ctx = executionContext(F.first(nodes()), UUID.randomUUID(), 0);
IgniteTypeFactory tf = ctx.getTypeFactory();

RelDataType rowType = TypeUtils.createRowType(tf, int.class, String.class);

ScanNode<Object[]> leftDeps = new ScanNode<>(ctx, rowType, Arrays.asList(
new Object[] {1, "Core"},
new Object[] {2, "SQL"},
new Object[] {3, "QA"}
));

rowType = TypeUtils.createRowType(tf, int.class, String.class, Integer.class);

ScanNode<Object[]> rightEmps = new ScanNode<>(ctx, rowType, Arrays.asList(
new Object[] {0, "Igor", 1},
new Object[] {1, "Roman", 2},
new Object[] {2, "Ivan", null},
new Object[] {3, "Alexey", 1}
));

RelDataType leftType = TypeUtils.createRowType(ctx.getTypeFactory(), int.class, String.class);
RelDataType rightType = TypeUtils.createRowType(ctx.getTypeFactory(), int.class, String.class, Integer.class);
RelDataType outType = TypeUtils.createRowType(ctx.getTypeFactory(), int.class, String.class, int.class,
String.class, Integer.class);

rowType = TypeUtils.createRowType(tf, int.class, String.class, String.class);

HashJoinNode<Object[]> join = HashJoinNode.create(ctx, outType, leftType, rightType, joinType,
JoinInfo.of(ImmutableIntList.of(0), ImmutableIntList.of(2)));

join.register(F.asList(leftDeps, rightEmps));

ProjectNode<Object[]> project = new ProjectNode<>(ctx, rowType,
r -> joinType == SEMI || joinType == ANTI ? new Object[] {r[1]} : new Object[] {r[2], r[3], r[1]});

project.register(join);

RootNode<Object[]> rootScan = new RootNode<>(ctx, rowType);

rootScan.register(project);

assert rootScan.hasNext();

ArrayList<Object[]> rows = new ArrayList<>();

while (rootScan.hasNext())
rows.add(rootScan.next());

checkDepartmentsJoinEmployeesResults(joinType, rows);
}
}

/** */
private void checkDepartmentsJoinEmployeesResults(JoinRelType joinType, ArrayList<Object[]> results) {
switch (joinType) {
case LEFT:
assertEquals(4, results.size());

Assert.assertArrayEquals(new Object[] {0, "Igor", "Core"}, results.get(0));
Assert.assertArrayEquals(new Object[] {3, "Alexey", "Core"}, results.get(1));
Assert.assertArrayEquals(new Object[] {1, "Roman", "SQL"}, results.get(2));
Assert.assertArrayEquals(new Object[] {null, null, "QA"}, results.get(3));
break;

case INNER:
assertEquals(3, results.size());

Assert.assertArrayEquals(new Object[] {0, "Igor", "Core"}, results.get(0));
Assert.assertArrayEquals(new Object[] {3, "Alexey", "Core"}, results.get(1));
Assert.assertArrayEquals(new Object[] {1, "Roman", "SQL"}, results.get(2));
break;

case RIGHT:
assertEquals(4, results.size());

Assert.assertArrayEquals(new Object[] {0, "Igor", "Core"}, results.get(0));
Assert.assertArrayEquals(new Object[] {3, "Alexey", "Core"}, results.get(1));
Assert.assertArrayEquals(new Object[] {1, "Roman", "SQL"}, results.get(2));
Assert.assertArrayEquals(new Object[] {2, "Ivan", null}, results.get(3));
break;

case FULL:
assertEquals(5, results.size());

Assert.assertArrayEquals(new Object[] {0, "Igor", "Core"}, results.get(0));
Assert.assertArrayEquals(new Object[] {3, "Alexey", "Core"}, results.get(1));
Assert.assertArrayEquals(new Object[] {1, "Roman", "SQL"}, results.get(2));
Assert.assertArrayEquals(new Object[] {null, null, "QA"}, results.get(3));
Assert.assertArrayEquals(new Object[] {2, "Ivan", null}, results.get(4));
break;

case SEMI:
assertEquals(2, results.size());

Assert.assertArrayEquals(new Object[] {"Core"}, results.get(0));
Assert.assertArrayEquals(new Object[] {"SQL"}, results.get(1));
break;

case ANTI:
assertEquals(1, results.size());

Assert.assertArrayEquals(new Object[] {"QA"}, results.get(0));
break;

default:
throw new IllegalArgumentException("Unknows join type.");
}
}

/** */
@Test
public void testMergeJoin() throws IgniteCheckedException {
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@

import java.sql.Date;
import java.util.LinkedHashMap;
import java.util.List;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.cache.CacheMode;
import org.apache.ignite.cache.QueryEntity;
@@ -278,37 +279,40 @@ public void testIndexLoopJoin() {

/** */
@Test
public void testMergeJoin() {
assertQuery("" +
"SELECT /*+ " + HintDefinition.MERGE_JOIN + " */ d1.name, d2.name FROM Developer d1, Developer d2 " +
"WHERE d1.depId = d2.depId")
.matches(containsSubPlan("IgniteMergeJoin"))
.returns("Bach", "Bach")
.returns("Beethoven", "Beethoven")
.returns("Beethoven", "Strauss")
.returns("Mozart", "Mozart")
.returns("Strauss", "Strauss")
.returns("Strauss", "Beethoven")
.returns("Vagner", "Vagner")
.returns("Chaikovsky", "Chaikovsky")
.returns("Verdy", "Verdy")
.returns("Stravinsky", "Stravinsky")
.returns("Rahmaninov", "Rahmaninov")
.returns("Shubert", "Shubert")
.returns("Glinka", "Glinka")
.returns("Arnalds", "Arnalds")
.returns("Glass", "Glass")
.returns("O'Halloran", "O'Halloran")
.returns("Prokofiev", "Prokofiev")
.returns("Yiruma", "Yiruma")
.returns("Cacciapaglia", "Cacciapaglia")
.returns("Einaudi", "Einaudi")
.returns("Hasaishi", "Hasaishi")
.returns("Marradi", "Marradi")
.returns("Musorgskii", "Musorgskii")
.returns("Rihter", "Rihter")
.returns("Zimmer", "Zimmer")
.check();
public void testMergeAndHashJoin() {
for (List<String> params : F.asList(F.asList(HintDefinition.MERGE_JOIN.name(), "IgniteMergeJoin"),
F.asList(HintDefinition.HASH_JOIN.name(), "IgniteHashJoin"))) {
assertQuery("" +
"SELECT /*+ " + params.get(0) + " */ d1.name, d2.name FROM Developer d1, Developer d2 " +
"WHERE d1.depId = d2.depId")
.matches(containsSubPlan(params.get(1)))
.returns("Bach", "Bach")
.returns("Beethoven", "Beethoven")
.returns("Beethoven", "Strauss")
.returns("Mozart", "Mozart")
.returns("Strauss", "Strauss")
.returns("Strauss", "Beethoven")
.returns("Vagner", "Vagner")
.returns("Chaikovsky", "Chaikovsky")
.returns("Verdy", "Verdy")
.returns("Stravinsky", "Stravinsky")
.returns("Rahmaninov", "Rahmaninov")
.returns("Shubert", "Shubert")
.returns("Glinka", "Glinka")
.returns("Arnalds", "Arnalds")
.returns("Glass", "Glass")
.returns("O'Halloran", "O'Halloran")
.returns("Prokofiev", "Prokofiev")
.returns("Yiruma", "Yiruma")
.returns("Cacciapaglia", "Cacciapaglia")
.returns("Einaudi", "Einaudi")
.returns("Hasaishi", "Hasaishi")
.returns("Marradi", "Marradi")
.returns("Musorgskii", "Musorgskii")
.returns("Rihter", "Rihter")
.returns("Zimmer", "Zimmer")
.check();
}
}

// ===== No filter =====
Original file line number Diff line number Diff line change
@@ -842,21 +842,32 @@ enum JoinType {
NESTED_LOOP(
"CorrelatedNestedLoopJoin",
"JoinCommuteRule",
"MergeJoinConverter"
"MergeJoinConverter",
"HashJoinConverter"
),

/** */
MERGE(
"CorrelatedNestedLoopJoin",
"JoinCommuteRule",
"NestedLoopJoinConverter"
"NestedLoopJoinConverter",
"HashJoinConverter"
),

/** */
CORRELATED(
"MergeJoinConverter",
"JoinCommuteRule",
"NestedLoopJoinConverter"
"NestedLoopJoinConverter",
"HashJoinConverter"
),

/** */
HASH(
"MergeJoinConverter",
"JoinCommuteRule",
"NestedLoopJoinConverter",
"CorrelatedNestedLoopJoin"
);

/** */
Original file line number Diff line number Diff line change
@@ -17,12 +17,15 @@

package org.apache.ignite.internal.processors.query.calcite.integration;

import java.util.List;
import org.apache.ignite.IgniteException;
import org.apache.ignite.cache.query.QueryCursor;
import org.apache.ignite.calcite.CalciteQueryEngineConfiguration;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.configuration.SqlConfiguration;
import org.apache.ignite.internal.processors.query.calcite.QueryChecker;
import org.apache.ignite.internal.processors.query.calcite.hint.HintDefinition;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.testframework.GridTestUtils;
import org.junit.Test;

@@ -234,22 +237,29 @@ public void testTableSpoolNode() {

/** */
@Test
public void testNestedLoopJoinNode() {
public void testRightMeterializedJoins() {
sql("CREATE TABLE tbl2 (id INT, b VARBINARY) WITH TEMPLATE=PARTITIONED");

for (int i = 0; i < 800; i++)
sql("INSERT INTO tbl2 VALUES (?, ?)", i, new byte[1000]);

assertQuery("SELECT /*+ NL_JOIN */ tbl.id, tbl.b, tbl2.id, tbl2.b FROM tbl JOIN tbl2 USING (id)")
.matches(QueryChecker.containsSubPlan("IgniteNestedLoopJoin"))
.resultSize(800)
.check();
List<List<String>> params = F.asList(F.asList(HintDefinition.NL_JOIN.name(), "NestedLoopJoin"),
F.asList(HintDefinition.HASH_JOIN.name(), "IgniteHashJoin"));

for (List<String> params0 : params) {
assertQuery("SELECT /*+ " + params0.get(0) + " */ tbl.id, tbl.b, tbl2.id, tbl2.b FROM tbl JOIN tbl2 USING (id)")
.matches(QueryChecker.containsSubPlan(params0.get(1)))
.resultSize(800)
.check();
}

for (int i = 800; i < 1000; i++)
sql("INSERT INTO tbl2 VALUES (?, ?)", i, new byte[1000]);

assertThrows("SELECT /*+ NL_JOIN */ tbl.id, tbl.b, tbl2.id, tbl2.b FROM tbl JOIN tbl2 USING (id)",
IgniteException.class, "Query quota exceeded");
for (List<String> paramSet : params) {
assertThrows("SELECT /*+ " + paramSet.get(0) + " */ tbl.id, tbl.b, tbl2.id, tbl2.b FROM tbl JOIN tbl2 USING (id)",
IgniteException.class, "Query quota exceeded");
}
}

/** */
Original file line number Diff line number Diff line change
@@ -54,7 +54,7 @@ public void testValidIndexExpressions() throws Exception {
IgniteRel phys = physicalPlan(
sql,
publicSchema,
"MergeJoinConverter", "NestedLoopJoinConverter"
"MergeJoinConverter", "NestedLoopJoinConverter", "HashJoinConverter"
);

System.out.println("+++ " + RelOptUtil.toString(phys));
Original file line number Diff line number Diff line change
@@ -84,7 +84,7 @@ public void testSingleKey() throws Exception {
IgniteRel phys = physicalPlan(
sql,
publicSchema,
"MergeJoinConverter", "NestedLoopJoinConverter", "FilterSpoolMergeToSortedIndexSpoolRule"
"MergeJoinConverter", "NestedLoopJoinConverter", "HashJoinConverter", "FilterSpoolMergeToSortedIndexSpoolRule"
);

System.out.println("+++\n" + RelOptUtil.toString(phys));
@@ -149,7 +149,7 @@ public void testMultipleKeys() throws Exception {
IgniteRel phys = physicalPlan(
sql,
publicSchema,
"MergeJoinConverter", "NestedLoopJoinConverter", "FilterSpoolMergeToSortedIndexSpoolRule"
"MergeJoinConverter", "NestedLoopJoinConverter", "HashJoinConverter", "FilterSpoolMergeToSortedIndexSpoolRule"
);

checkSplitAndSerialization(phys, publicSchema);
@@ -212,7 +212,7 @@ public void testSourceWithoutCollation() throws Exception {
IgniteRel phys = physicalPlan(
sql,
publicSchema,
"MergeJoinConverter", "NestedLoopJoinConverter"
"MergeJoinConverter", "NestedLoopJoinConverter", "HashJoinConverter"
);

checkSplitAndSerialization(phys, publicSchema);
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/*
* 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.ignite.internal.processors.query.calcite.planner;

import java.util.List;
import org.apache.calcite.plan.RelOptPlanner.CannotPlanException;
import org.apache.calcite.rel.RelCollations;
import org.apache.calcite.rel.RelFieldCollation;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Join;
import org.apache.calcite.util.ImmutableIntList;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteHashJoin;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteSort;
import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
import org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.CU;
import org.jetbrains.annotations.Nullable;
import org.junit.Test;

import static org.apache.calcite.rel.RelFieldCollation.Direction.ASCENDING;
import static org.apache.ignite.testframework.GridTestUtils.assertThrows;

/** */
public class HashJoinPlannerTest extends AbstractPlannerTest {
/** */
private static final String[] DISABLED_RULES = {"NestedLoopJoinConverter", "CorrelatedNestedLoopJoin", "MergeJoinConverter",
"JoinCommuteRule"};

/** */
private static final String[] JOIN_TYPES = {"LEFT", "RIGHT", "INNER", "FULL"};

/** */
@Test
public void testHashJoinKeepsLeftCollation() throws Exception {
TestTable tbl1 = createSimpleTable();
TestTable tbl2 = createComplexTable();

IgniteSchema schema = createSchema(tbl1, tbl2);

String sql = "select t1.ID, t2.ID1 "
+ "from TEST_TBL_CMPLX t2 "
+ "join TEST_TBL t1 on t1.id = t2.id1 "
+ "order by t2.ID1 NULLS LAST, t2.ID2 NULLS LAST";

RelNode plan = physicalPlan(sql, schema, DISABLED_RULES);

assertEquals(0, findNodes(plan, byClass(IgniteSort.class)).size());
assertEquals(1, findNodes(plan, byClass(IgniteHashJoin.class)).size());
assertNotNull(findFirstNode(plan, byClass(IgniteHashJoin.class)));
}

/** */
@Test
public void testHashJoinErasesRightCollation() throws Exception {
TestTable tbl1 = createSimpleTable();
TestTable tbl2 = createComplexTable();

IgniteSchema schema = createSchema(tbl1, tbl2);

String sql = "select t1.ID, t2.ID1 "
+ "from TEST_TBL t1 "
+ "join TEST_TBL_CMPLX t2 on t1.id = t2.id1 "
+ "order by t2.ID1 NULLS LAST, t2.ID2 NULLS LAST";

IgniteRel plan = physicalPlan(sql, schema, DISABLED_RULES);

assertNotNull(findFirstNode(plan, byClass(IgniteHashJoin.class)));
assertNotNull(sortOnTopOfJoin(plan));
}

/** */
@Test
public void testHashJoinWinsOnSkewedLeftInput() throws Exception {
TestTable smallTbl = createSimpleTable("SMALL_TBL", 1000);
TestTable largeTbl = createSimpleTable("LARGE_TBL", 500_000);

IgniteSchema schema = createSchema(smallTbl, largeTbl);

assertPlan(
"select t1.ID, t1.INT_VAL, t2.ID, t2.INT_VAL from LARGE_TBL t1 join SMALL_TBL t2 on t1.INT_VAL = t2.INT_VAL",
schema,
nodeOrAnyChild(isInstanceOf(IgniteHashJoin.class)),
"JoinCommuteRule"
);

assertPlan(
"select t1.ID, t1.INT_VAL, t2.ID, t2.INT_VAL from SMALL_TBL t1 join LARGE_TBL t2 on t1.INT_VAL = t2.INT_VAL",
schema,
nodeOrAnyChild(isInstanceOf(IgniteHashJoin.class).negate()),
"JoinCommuteRule"
);

// Merge join can consume less CPU resources.
assertPlan(
"select t1.ID, t1.INT_VAL, t2.ID, t2.INT_VAL from SMALL_TBL t1 join LARGE_TBL t2 on t1.ID = t2.ID",
schema,
nodeOrAnyChild(isInstanceOf(IgniteHashJoin.class).negate()),
"JoinCommuteRule"
);
}

/** */
private static @Nullable IgniteSort sortOnTopOfJoin(IgniteRel root) {
List<IgniteSort> sortNodes = findNodes(root, byClass(IgniteSort.class)
.and(node -> node.getInputs().size() == 1 && node.getInput(0) instanceof Join));

if (sortNodes.size() > 1)
throw new IllegalStateException("Incorrect sort nodes number: expected 1, actual " + sortNodes.size());

return sortNodes.isEmpty() ? null : sortNodes.get(0);
}

/** */
@Test
public void testHashJoinApplied() throws Exception {
for (List<Object> paramSet : testJoinIsAppliedParameters()) {
assert paramSet != null && paramSet.size() == 2;

String sql = (String)paramSet.get(0);

boolean canBePlanned = (Boolean)paramSet.get(1);

TestTable tbl = createTable("T1", IgniteDistributions.single(), "ID", Integer.class, "C1", Integer.class);

IgniteSchema schema = createSchema(tbl);

for (String type : JOIN_TYPES) {
String sql0 = String.format(sql, type);

if (canBePlanned)
assertPlan(sql0, schema, nodeOrAnyChild(isInstanceOf(IgniteHashJoin.class)), DISABLED_RULES);
else {
assertThrows(null, () -> physicalPlan(sql0, schema, DISABLED_RULES), CannotPlanException.class,
"There are not enough rules");
}
}
}
}

/** */
private static List<List<Object>> testJoinIsAppliedParameters() {
return F.asList(
F.asList("select t1.c1 from t1 %s join t1 t2 using(c1)", true),
F.asList("select t1.c1 from t1 %s join t1 t2 on t1.c1 = t2.c1", true),
F.asList("select t1.c1 from t1 %s join t1 t2 ON t1.id is not distinct from t2.c1", false),
F.asList("select t1.c1 from t1 %s join t1 t2 on t1.c1 = OCTET_LENGTH('TEST')", false),
F.asList("select t1.c1 from t1 %s join t1 t2 on t1.c1 = LOG10(t1.c1)", false),
F.asList("select t1.c1 from t1 %s join t1 t2 on t1.c1 = t2.c1 and t1.ID > t2.ID", false),
F.asList("select t1.c1 from t1 %s join t1 t2 on t1.c1 = 1 and t2.c1 = 1", false),
F.asList("select t1.c1 from t1 %s join t1 t2 on t1.c1 = 1", false),
F.asList("select t1.c1 from t1 %s join t1 t2 on t1.c1 = ?", false)
);
}

/** */
private static TestTable createSimpleTable() {
return createSimpleTable("TEST_TBL", DEFAULT_TBL_SIZE);
}

/** */
private static TestTable createSimpleTable(String name, int size) {
return createTable(
name,
size,
IgniteDistributions.affinity(0, CU.cacheId("default"), 0),
"ID", Integer.class,
"INT_VAL", Integer.class,
"STR_VAL", String.class
).addIndex(
RelCollations.of(new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.LAST)),
"PK"
);
}

/** */
private static TestTable createComplexTable() {
return createTable(
"TEST_TBL_CMPLX",
DEFAULT_TBL_SIZE,
IgniteDistributions.affinity(ImmutableIntList.of(0, 1), CU.cacheId("default"), 0),
"ID1", Integer.class,
"ID2", Integer.class,
"STR_VAL", String.class
).addIndex(
RelCollations.of(
new RelFieldCollation(0, ASCENDING, RelFieldCollation.NullDirection.LAST),
new RelFieldCollation(1, ASCENDING, RelFieldCollation.NullDirection.LAST)
),
"PK"
);
}
}
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.util.ImmutableIntList;
import org.apache.ignite.internal.processors.query.calcite.rel.AbstractIgniteJoin;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteMergeJoin;
@@ -42,6 +43,9 @@
* Test suite to verify join colocation.
*/
public class JoinColocationPlannerTest extends AbstractPlannerTest {
/** */
private static final String[] DISABLED_RULES = new String[] {"HashJoinConverter", "MergeJoinConverter"};

/**
* Join of the same tables with a simple affinity is expected to be colocated.
*/
@@ -62,16 +66,18 @@ public void joinSameTableSimpleAff() throws Exception {
"from TEST_TBL t1 " +
"join TEST_TBL t2 on t1.id = t2.id";

RelNode phys = physicalPlan(sql, schema, "NestedLoopJoinConverter", "CorrelatedNestedLoopJoin");
for (String disabledRule : DISABLED_RULES) {
RelNode phys = physicalPlan(sql, schema, "NestedLoopJoinConverter", "CorrelatedNestedLoopJoin", disabledRule);

IgniteMergeJoin join = findFirstNode(phys, byClass(IgniteMergeJoin.class));
AbstractIgniteJoin join = findFirstNode(phys, byClass(AbstractIgniteJoin.class));

String invalidPlanMsg = "Invalid plan:\n" + RelOptUtil.toString(phys);
String invalidPlanMsg = "Invalid plan:\n" + RelOptUtil.toString(phys);

assertThat(invalidPlanMsg, join, notNullValue());
assertThat(invalidPlanMsg, join.distribution().function().affinity(), is(true));
assertThat(invalidPlanMsg, join.getLeft(), instanceOf(IgniteIndexScan.class));
assertThat(invalidPlanMsg, join.getRight(), instanceOf(IgniteIndexScan.class));
assertThat(invalidPlanMsg, join, notNullValue());
assertThat(invalidPlanMsg, join.distribution().function().affinity(), is(true));
assertThat(invalidPlanMsg, join.getLeft(), instanceOf(IgniteIndexScan.class));
assertThat(invalidPlanMsg, join.getRight(), instanceOf(IgniteIndexScan.class));
}
}

/**
@@ -95,16 +101,18 @@ public void joinSameTableComplexAff() throws Exception {
"from TEST_TBL t1 " +
"join TEST_TBL t2 on t1.id1 = t2.id1 and t1.id2 = t2.id2";

RelNode phys = physicalPlan(sql, schema, "NestedLoopJoinConverter", "CorrelatedNestedLoopJoin");
for (String disabledRule : DISABLED_RULES) {
RelNode phys = physicalPlan(sql, schema, "NestedLoopJoinConverter", "CorrelatedNestedLoopJoin", disabledRule);

IgniteMergeJoin join = findFirstNode(phys, byClass(IgniteMergeJoin.class));
AbstractIgniteJoin join = findFirstNode(phys, byClass(AbstractIgniteJoin.class));

String invalidPlanMsg = "Invalid plan:\n" + RelOptUtil.toString(phys);
String invalidPlanMsg = "Invalid plan:\n" + RelOptUtil.toString(phys);

assertThat(invalidPlanMsg, join, notNullValue());
assertThat(invalidPlanMsg, join.distribution().function().affinity(), is(true));
assertThat(invalidPlanMsg, join.getLeft(), instanceOf(IgniteIndexScan.class));
assertThat(invalidPlanMsg, join.getRight(), instanceOf(IgniteIndexScan.class));
assertThat(invalidPlanMsg, join, notNullValue());
assertThat(invalidPlanMsg, join.distribution().function().affinity(), is(true));
assertThat(invalidPlanMsg, join.getLeft(), instanceOf(IgniteIndexScan.class));
assertThat(invalidPlanMsg, join.getRight(), instanceOf(IgniteIndexScan.class));
}
}

/**
@@ -140,22 +148,24 @@ public void joinComplexToSimpleAff() throws Exception {
"from COMPLEX_TBL t1 " +
"join SIMPLE_TBL t2 on t1.id1 = t2.id";

RelNode phys = physicalPlan(sql, schema, "NestedLoopJoinConverter", "CorrelatedNestedLoopJoin");
for (String disabledRule : DISABLED_RULES) {
RelNode phys = physicalPlan(sql, schema, "NestedLoopJoinConverter", "CorrelatedNestedLoopJoin", disabledRule);

IgniteMergeJoin join = findFirstNode(phys, byClass(IgniteMergeJoin.class));
AbstractIgniteJoin join = findFirstNode(phys, byClass(AbstractIgniteJoin.class));

String invalidPlanMsg = "Invalid plan:\n" + RelOptUtil.toString(phys);
String invalidPlanMsg = "Invalid plan:\n" + RelOptUtil.toString(phys);

assertThat(invalidPlanMsg, join, notNullValue());
assertThat(invalidPlanMsg, join.distribution().function().affinity(), is(true));
assertThat(invalidPlanMsg, join, notNullValue());
assertThat(invalidPlanMsg, join.distribution().function().affinity(), is(true));

List<IgniteExchange> exchanges = findNodes(phys, node -> node instanceof IgniteExchange
&& ((IgniteRel)node).distribution().function().affinity());
List<IgniteExchange> exchanges = findNodes(phys, node -> node instanceof IgniteExchange
&& ((IgniteRel)node).distribution().function().affinity());

assertThat(invalidPlanMsg, exchanges, hasSize(1));
assertThat(invalidPlanMsg, exchanges.get(0).getInput(0), instanceOf(IgniteIndexScan.class));
assertThat(invalidPlanMsg, exchanges.get(0).getInput(0)
.getTable().unwrap(TestTable.class), equalTo(complexTbl));
assertThat(invalidPlanMsg, exchanges, hasSize(1));
assertThat(invalidPlanMsg, exchanges.get(0).getInput(0), instanceOf(IgniteIndexScan.class));
assertThat(invalidPlanMsg, exchanges.get(0).getInput(0)
.getTable().unwrap(TestTable.class), equalTo(complexTbl));
}
}

/**
@@ -189,13 +199,15 @@ public void joinComplexToComplexAffWithDifferentOrder() throws Exception {
"from COMPLEX_TBL_DIRECT t1 " +
"join COMPLEX_TBL_INDIRECT t2 on t1.id1 = t2.id1 and t1.id2 = t2.id2";

RelNode phys = physicalPlan(sql, schema, "NestedLoopJoinConverter", "CorrelatedNestedLoopJoin");
for (String disabledRule : DISABLED_RULES) {
RelNode phys = physicalPlan(sql, schema, "NestedLoopJoinConverter", "CorrelatedNestedLoopJoin", disabledRule);

IgniteMergeJoin exchange = findFirstNode(phys, node -> node instanceof IgniteExchange
&& ((IgniteRel)node).distribution().function().affinity());
IgniteMergeJoin exchange = findFirstNode(phys, node -> node instanceof IgniteExchange
&& ((IgniteRel)node).distribution().function().affinity());

String invalidPlanMsg = "Invalid plan:\n" + RelOptUtil.toString(phys);
String invalidPlanMsg = "Invalid plan:\n" + RelOptUtil.toString(phys);

assertThat(invalidPlanMsg, exchange, nullValue());
assertThat(invalidPlanMsg, exchange, nullValue());
}
}
}
Original file line number Diff line number Diff line change
@@ -81,8 +81,8 @@ public class JoinCommutePlannerTest extends AbstractPlannerTest {
public void testOuterCommute() throws Exception {
String sql = "SELECT COUNT(*) FROM SMALL s RIGHT JOIN HUGE h on h.id = s.id";

IgniteRel phys = physicalPlan(sql, publicSchema,
"MergeJoinConverter", "CorrelatedNestedLoopJoin");
IgniteRel phys = physicalPlan(sql, publicSchema, "MergeJoinConverter", "CorrelatedNestedLoopJoin",
"HashJoinConverter");

assertNotNull(phys);

@@ -94,8 +94,8 @@ public void testOuterCommute() throws Exception {

assertEquals(JoinRelType.LEFT, join.getJoinType());

PlanningContext ctx = plannerCtx(sql, publicSchema, "MergeJoinConverter",
"CorrelatedNestedLoopJoin");
PlanningContext ctx = plannerCtx(sql, publicSchema, "MergeJoinConverter", "CorrelatedNestedLoopJoin",
"HashJoinConverter");

RelOptPlanner pl = ctx.cluster().getPlanner();

@@ -105,8 +105,8 @@ public void testOuterCommute() throws Exception {

assertNotNull(phys);

phys = physicalPlan(sql, publicSchema,
"MergeJoinConverter", "CorrelatedNestedLoopJoin", "JoinCommuteRule");
phys = physicalPlan(sql, publicSchema, "MergeJoinConverter", "CorrelatedNestedLoopJoin", "HashJoinConverter",
"JoinCommuteRule");

join = findFirstNode(phys, byClass(IgniteNestedLoopJoin.class));

@@ -117,8 +117,8 @@ public void testOuterCommute() throws Exception {
// no commute
assertEquals(JoinRelType.RIGHT, join.getJoinType());

ctx = plannerCtx(sql, publicSchema,
"MergeJoinConverter", "CorrelatedNestedLoopJoin", "JoinCommuteRule");
ctx = plannerCtx(sql, publicSchema, "MergeJoinConverter", "CorrelatedNestedLoopJoin", "HashJoinConverter",
"JoinCommuteRule");

pl = ctx.cluster().getPlanner();

@@ -134,8 +134,8 @@ public void testOuterCommute() throws Exception {
public void testInnerCommute() throws Exception {
String sql = "SELECT COUNT(*) FROM SMALL s JOIN HUGE h on h.id = s.id";

IgniteRel phys = physicalPlan(sql, publicSchema,
"MergeJoinConverter", "CorrelatedNestedLoopJoin");
IgniteRel phys = physicalPlan(sql, publicSchema, "MergeJoinConverter", "CorrelatedNestedLoopJoin",
"HashJoinConverter");

assertNotNull(phys);

@@ -162,8 +162,8 @@ public void testInnerCommute() throws Exception {

assertEquals(JoinRelType.INNER, join.getJoinType());

PlanningContext ctx = plannerCtx(sql, publicSchema, "MergeJoinConverter",
"CorrelatedNestedLoopJoin");
PlanningContext ctx = plannerCtx(sql, publicSchema, "MergeJoinConverter", "CorrelatedNestedLoopJoin",
"HashJoinConverter");

RelOptPlanner pl = ctx.cluster().getPlanner();

@@ -173,8 +173,8 @@ public void testInnerCommute() throws Exception {

assertNotNull(phys);

phys = physicalPlan(sql, publicSchema,
"MergeJoinConverter", "CorrelatedNestedLoopJoin", "JoinCommuteRule");
phys = physicalPlan(sql, publicSchema, "MergeJoinConverter", "CorrelatedNestedLoopJoin", "HashJoinConverter",
"JoinCommuteRule");

join = findFirstNode(phys, byClass(IgniteNestedLoopJoin.class));
proj = findFirstNode(phys, byClass(IgniteProject.class));
@@ -200,8 +200,8 @@ public void testInnerCommute() throws Exception {
// no commute
assertEquals(JoinRelType.INNER, join.getJoinType());

ctx = plannerCtx(sql, publicSchema,
"MergeJoinConverter", "CorrelatedNestedLoopJoin", "JoinCommuteRule");
ctx = plannerCtx(sql, publicSchema, "MergeJoinConverter", "CorrelatedNestedLoopJoin", "HashJoinConverter",
"JoinCommuteRule");

pl = ctx.cluster().getPlanner();

Original file line number Diff line number Diff line change
@@ -41,7 +41,8 @@ public class MergeJoinPlannerTest extends AbstractPlannerTest {
"NestedLoopJoinConverter",
"CorrelatedNestedLoopJoin",
"FilterSpoolMergeRule",
"JoinCommuteRule"
"JoinCommuteRule",
"HashJoinConverter"
};

/**
Original file line number Diff line number Diff line change
@@ -90,7 +90,7 @@ public void testNotColocatedEqJoin() throws Exception {
IgniteRel phys = physicalPlan(
sql,
publicSchema,
"MergeJoinConverter", "NestedLoopJoinConverter", "FilterSpoolMergeToHashIndexSpoolRule"
"MergeJoinConverter", "NestedLoopJoinConverter", "HashJoinConverter", "FilterSpoolMergeToHashIndexSpoolRule"
);

checkSplitAndSerialization(phys, publicSchema);
@@ -157,7 +157,7 @@ public void testPartialIndexForCondition() throws Exception {
IgniteRel phys = physicalPlan(
sql,
publicSchema,
"MergeJoinConverter", "NestedLoopJoinConverter", "FilterSpoolMergeToHashIndexSpoolRule"
"MergeJoinConverter", "NestedLoopJoinConverter", "HashJoinConverter", "FilterSpoolMergeToHashIndexSpoolRule"
);

System.out.println("+++ \n" + RelOptUtil.toString(phys));
@@ -252,7 +252,7 @@ public void testRestoreCollation() throws Exception {
.and(input(1, isInstanceOf(IgniteSortedIndexSpool.class)
.and(spool -> spool.collation().getFieldCollations().get(0).getFieldIndex() == equalIdx)
))),
"MergeJoinConverter", "NestedLoopJoinConverter", "FilterSpoolMergeToHashIndexSpoolRule"
"MergeJoinConverter", "NestedLoopJoinConverter", "HashJoinConverter", "FilterSpoolMergeToHashIndexSpoolRule"
);
}
}
Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@
import org.apache.ignite.internal.processors.query.calcite.planner.TestTable;
import org.apache.ignite.internal.processors.query.calcite.rel.AbstractIgniteJoin;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteCorrelatedNestedLoopJoin;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteHashJoin;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteMergeJoin;
import org.apache.ignite.internal.processors.query.calcite.rel.IgniteNestedLoopJoin;
import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
@@ -36,9 +37,11 @@
import org.junit.Test;

import static org.apache.ignite.internal.processors.query.calcite.hint.HintDefinition.CNL_JOIN;
import static org.apache.ignite.internal.processors.query.calcite.hint.HintDefinition.HASH_JOIN;
import static org.apache.ignite.internal.processors.query.calcite.hint.HintDefinition.MERGE_JOIN;
import static org.apache.ignite.internal.processors.query.calcite.hint.HintDefinition.NL_JOIN;
import static org.apache.ignite.internal.processors.query.calcite.hint.HintDefinition.NO_CNL_JOIN;
import static org.apache.ignite.internal.processors.query.calcite.hint.HintDefinition.NO_HASH_JOIN;
import static org.apache.ignite.internal.processors.query.calcite.hint.HintDefinition.NO_MERGE_JOIN;
import static org.apache.ignite.internal.processors.query.calcite.hint.HintDefinition.NO_NL_JOIN;

@@ -120,12 +123,12 @@ public void testHintsErrors() throws Exception {
// Following hint must not override leading.
lsnrLog.clearListeners();

lsnr = LogListener.matches("Skipped hint '" + NL_JOIN.name() + "'")
lsnr = LogListener.matches("Skipped hint '" + HASH_JOIN.name() + "'")
.andMatches("This join type is already disabled or forced to use before").build();

lsnrLog.registerListener(lsnr);

physicalPlan("SELECT /*+ " + MERGE_JOIN + "(TBL1)," + NL_JOIN + "(TBL1,TBL2) */ t1.v1, t2.v2 FROM TBL1 " +
physicalPlan("SELECT /*+ " + MERGE_JOIN + "(TBL1)," + HASH_JOIN + "(TBL1,TBL2) */ t1.v1, t2.v2 FROM TBL1 " +
"t1 JOIN TBL2 t2 on t1.v3=t2.v3", schema);

assertTrue(lsnr.check());
@@ -140,7 +143,7 @@ public void testHintsErrors() throws Exception {

physicalPlan("SELECT /*+ " + NL_JOIN + "(TBL1) */ t1.v1, t2.v2 FROM TBL1 " +
"t1 JOIN TBL2 t2 on t1.v3=t2.v3 where t2.v1 in " +
"(SELECT /*+ " + MERGE_JOIN + "(TBL1), " + CNL_JOIN + "(TBL1,TBL3) */ t3.v3 from TBL3 t3 JOIN TBL1 t4 " +
"(SELECT /*+ " + HASH_JOIN + "(TBL1), " + CNL_JOIN + "(TBL1,TBL3) */ t3.v3 from TBL3 t3 JOIN TBL1 t4 " +
"on t3.v2=t4.v2)", schema, CORE_JOIN_REORDER_RULES);

assertTrue(lsnr.check());
@@ -186,9 +189,9 @@ public void testHintsErrors() throws Exception {
*/
@Test
public void testDisableNLJoin() throws Exception {
for (HintDefinition hint : Arrays.asList(NO_NL_JOIN, CNL_JOIN, MERGE_JOIN)) {
for (HintDefinition hint : Arrays.asList(NO_NL_JOIN, CNL_JOIN, MERGE_JOIN, HASH_JOIN)) {
doTestDisableJoinTypeWith("TBL5", "TBL4", "INNER", IgniteNestedLoopJoin.class,
NO_NL_JOIN, "MergeJoinConverter");
NO_NL_JOIN, "MergeJoinConverter", "HashJoinConverter");

doTestDisableJoinTypeWith("TBL3", "TBL1", "LEFT", IgniteNestedLoopJoin.class,
NO_NL_JOIN);
@@ -210,15 +213,34 @@ public void testDisableNLJoin() throws Exception {
@Test
public void testDisableMergeJoin() throws Exception {
for (HintDefinition hint : Arrays.asList(NO_MERGE_JOIN, NL_JOIN, CNL_JOIN)) {
doTestDisableJoinTypeWith("TBL4", "TBL2", "INNER", IgniteMergeJoin.class, hint);
doTestDisableJoinTypeWith("TBL4", "TBL2", "INNER", IgniteMergeJoin.class, hint, "HashJoinConverter");

doTestDisableJoinTypeWith("TBL4", "TBL2", "LEFT", IgniteMergeJoin.class, hint);
doTestDisableJoinTypeWith("TBL4", "TBL2", "LEFT", IgniteMergeJoin.class, hint, "HashJoinConverter");

// Correlated nested loop join supports only INNER and LEFT join types.
if (hint != CNL_JOIN) {
doTestDisableJoinTypeWith("TBL4", "TBL2", "RIGHT", IgniteMergeJoin.class, hint);
doTestDisableJoinTypeWith("TBL4", "TBL2", "RIGHT", IgniteMergeJoin.class, hint, "HashJoinConverter");

doTestDisableJoinTypeWith("TBL4", "TBL2", "FULL", IgniteMergeJoin.class, hint);
doTestDisableJoinTypeWith("TBL4", "TBL2", "FULL", IgniteMergeJoin.class, hint, "HashJoinConverter");
}
}
}

/**
* Tests hash join is disabled by hints.
*/
@Test
public void testDisableHashJoin() throws Exception {
for (HintDefinition hint : Arrays.asList(NO_HASH_JOIN, MERGE_JOIN, NL_JOIN, CNL_JOIN)) {
doTestDisableJoinTypeWith("TBL4", "TBL2", "INNER", IgniteHashJoin.class, hint);

doTestDisableJoinTypeWith("TBL4", "TBL2", "LEFT", IgniteHashJoin.class, hint);

// Correlated nested loop join supports only INNER and LEFT join types.
if (hint != CNL_JOIN) {
doTestDisableJoinTypeWith("TBL4", "TBL2", "RIGHT", IgniteHashJoin.class, hint);

doTestDisableJoinTypeWith("TBL4", "TBL2", "FULL", IgniteHashJoin.class, hint);
}
}
}
@@ -241,6 +263,24 @@ public void testMergeJoinEnabled() throws Exception {
MERGE_JOIN, IgniteMergeJoin.class, CORE_JOIN_REORDER_RULES);
}

/**
* Tests the hash join is enabled by the hint instead of the other joins.
*/
@Test
public void testHashJoinEnabled() throws Exception {
doTestCertainJoinTypeEnabled("TBL1", "INNER", "TBL2", IgniteCorrelatedNestedLoopJoin.class,
HASH_JOIN, IgniteHashJoin.class);

doTestCertainJoinTypeEnabled("TBL1", "RIGHT", "TBL2", IgniteNestedLoopJoin.class,
HASH_JOIN, IgniteHashJoin.class);

doTestCertainJoinTypeEnabled("TBL1", "INNER", "TBL2", IgniteCorrelatedNestedLoopJoin.class,
HASH_JOIN, IgniteHashJoin.class, CORE_JOIN_REORDER_RULES);

doTestCertainJoinTypeEnabled("TBL1", "RIGHT", "TBL2", IgniteNestedLoopJoin.class,
HASH_JOIN, IgniteHashJoin.class, CORE_JOIN_REORDER_RULES);
}

/**
* Tests the nested loop join is enabled by the hint instead of the other joins.
*/
@@ -249,13 +289,13 @@ public void testNLJoinEnabled() throws Exception {
doTestCertainJoinTypeEnabled("TBL2", "INNER", "TBL1", IgniteCorrelatedNestedLoopJoin.class,
NL_JOIN, IgniteNestedLoopJoin.class);

doTestCertainJoinTypeEnabled("TBL5", "INNER", "TBL4", IgniteMergeJoin.class,
doTestCertainJoinTypeEnabled("TBL5", "INNER", "TBL4", IgniteHashJoin.class,
NL_JOIN, IgniteNestedLoopJoin.class);

doTestCertainJoinTypeEnabled("TBL1", "LEFT", "TBL2", IgniteCorrelatedNestedLoopJoin.class,
NL_JOIN, IgniteNestedLoopJoin.class, CORE_JOIN_REORDER_RULES);

doTestCertainJoinTypeEnabled("TBL5", "INNER", "TBL4", IgniteMergeJoin.class,
doTestCertainJoinTypeEnabled("TBL5", "INNER", "TBL4", IgniteHashJoin.class,
NL_JOIN, IgniteNestedLoopJoin.class, CORE_JOIN_REORDER_RULES);
}

@@ -281,7 +321,7 @@ public void testCNLJoinEnabled() throws Exception {
doTestCertainJoinTypeEnabled("TBL2", "LEFT", "TBL1", IgniteNestedLoopJoin.class,
CNL_JOIN, IgniteCorrelatedNestedLoopJoin.class);

doTestCertainJoinTypeEnabled("TBL5", "INNER", "TBL4", IgniteMergeJoin.class,
doTestCertainJoinTypeEnabled("TBL5", "INNER", "TBL4", IgniteHashJoin.class,
CNL_JOIN, IgniteCorrelatedNestedLoopJoin.class);

// Even CNL join doesn't support RIGHT join, join type and join inputs might be switched by Calcite.
@@ -291,7 +331,7 @@ public void testCNLJoinEnabled() throws Exception {
doTestCertainJoinTypeEnabled("TBL2", "LEFT", "TBL1", IgniteNestedLoopJoin.class,
CNL_JOIN, IgniteCorrelatedNestedLoopJoin.class, CORE_JOIN_REORDER_RULES);

doTestCertainJoinTypeEnabled("TBL5", "INNER", "TBL4", IgniteMergeJoin.class,
doTestCertainJoinTypeEnabled("TBL5", "INNER", "TBL4", IgniteHashJoin.class,
CNL_JOIN, IgniteCorrelatedNestedLoopJoin.class, CORE_JOIN_REORDER_RULES);
}

@@ -387,15 +427,15 @@ public void testTableHints() throws Exception {
.and(input(1, nodeOrAnyChild(isTableScan("TBL1")))))), CORE_JOIN_REORDER_RULES);

// Table hint has a bigger priority. Leading CNL_JOIN is ignored.
assertPlan(String.format(sqlTpl, "/*+ " + CNL_JOIN + " */", "/*+ " + NL_JOIN + " */", "/*+ " + MERGE_JOIN + " */"),
assertPlan(String.format(sqlTpl, "/*+ " + CNL_JOIN + " */", "/*+ " + NL_JOIN + " */", "/*+ " + HASH_JOIN + " */"),
schema, nodeOrAnyChild(isInstanceOf(IgniteNestedLoopJoin.class).and(input(1, isTableScan("TBL3"))))
.and(nodeOrAnyChild(isInstanceOf(IgniteMergeJoin.class)
.and(nodeOrAnyChild(isInstanceOf(IgniteHashJoin.class)
.and(input(1, nodeOrAnyChild(isTableScan("TBL1")))))), CORE_JOIN_REORDER_RULES);

// Leading query hint works only for the second join.
assertPlan(String.format(sqlTpl, "/*+ " + CNL_JOIN + " */", "/*+ " + NL_JOIN + " */", ""), schema,
assertPlan(String.format(sqlTpl, "/*+ " + HASH_JOIN + " */", "/*+ " + NL_JOIN + " */", ""), schema,
nodeOrAnyChild(isInstanceOf(IgniteNestedLoopJoin.class).and(input(1, isTableScan("TBL3"))))
.and(nodeOrAnyChild(isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)
.and(nodeOrAnyChild(isInstanceOf(IgniteHashJoin.class)
.and(input(1, nodeOrAnyChild(isTableScan("TBL1")))))), CORE_JOIN_REORDER_RULES);

// Table hint with wrong table name is ignored.
@@ -499,13 +539,13 @@ public void testSeveralDisables() throws Exception {
assertPlan(String.format(sqlTpl, "/*+ " + NO_CNL_JOIN + ',' + NO_NL_JOIN + " */"), schema,
nodeOrAnyChild(isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)).negate()
.and(nodeOrAnyChild(isInstanceOf(IgniteNestedLoopJoin.class)).negate())
.and(nodeOrAnyChild(isInstanceOf(IgniteMergeJoin.class).and(hasNestedTableScan("TBL1"))
.and(nodeOrAnyChild(isInstanceOf(IgniteHashJoin.class).and(hasNestedTableScan("TBL1"))
.and(hasNestedTableScan("TBL2")))), CORE_JOIN_REORDER_RULES);

assertPlan(String.format(sqlTpl, "/*+ " + NO_CNL_JOIN + "(TBL1)," + NO_NL_JOIN + "(TBL2) */"), schema,
nodeOrAnyChild(isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)).negate()
.and(nodeOrAnyChild(isInstanceOf(IgniteNestedLoopJoin.class)).negate())
.and(nodeOrAnyChild(isInstanceOf(IgniteMergeJoin.class).and(hasNestedTableScan("TBL1"))
.and(nodeOrAnyChild(isInstanceOf(IgniteHashJoin.class).and(hasNestedTableScan("TBL1"))
.and(hasNestedTableScan("TBL2")))), CORE_JOIN_REORDER_RULES);

// Check with forcing in the middle.
@@ -530,7 +570,7 @@ schema, nodeOrAnyChild(isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)).negat
assertPlan(String.format(sqlTpl, "/*+ " + NO_CNL_JOIN + ',' + NO_NL_JOIN + ',' + NO_MERGE_JOIN + " */"), schema,
nodeOrAnyChild(isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)).negate()
.and(nodeOrAnyChild(isInstanceOf(IgniteNestedLoopJoin.class)).negate())
.and(nodeOrAnyChild(isInstanceOf(IgniteMergeJoin.class).and(hasNestedTableScan("TBL1"))
.and(nodeOrAnyChild(isInstanceOf(IgniteHashJoin.class).and(hasNestedTableScan("TBL1"))
.and(hasNestedTableScan("TBL2")))), CORE_JOIN_REORDER_RULES);

// Check many duplicated disables doesn't erase other disables.
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@
import org.apache.ignite.internal.processors.query.calcite.planner.DataTypesPlannerTest;
import org.apache.ignite.internal.processors.query.calcite.planner.HashAggregatePlannerTest;
import org.apache.ignite.internal.processors.query.calcite.planner.HashIndexSpoolPlannerTest;
import org.apache.ignite.internal.processors.query.calcite.planner.HashJoinPlannerTest;
import org.apache.ignite.internal.processors.query.calcite.planner.IndexRebuildPlannerTest;
import org.apache.ignite.internal.processors.query.calcite.planner.IndexSearchBoundsPlannerTest;
import org.apache.ignite.internal.processors.query.calcite.planner.InlineIndexScanPlannerTest;
@@ -72,6 +73,7 @@
JoinCommutePlannerTest.class,
LimitOffsetPlannerTest.class,
MergeJoinPlannerTest.class,
HashJoinPlannerTest.class,
StatisticsPlannerTest.class,
CorrelatedSubqueryPlannerTest.class,
JoinWithUsingPlannerTest.class,
8 changes: 7 additions & 1 deletion modules/calcite/src/test/sql/sqlite/join/join1.test
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@ statement ok
INSERT INTO t2 VALUES(3,4,5);

query III rowsort
SELECT /*+ CNL_JOIN */ t2.* FROM t2 NATURAL JOIN t1;
SELECT /*+ NL_JOIN */ t2.* FROM t2 NATURAL JOIN t1;
----
2 3 4
3 4 5
@@ -41,6 +41,12 @@ SELECT /*+ MERGE_JOIN */ t2.* FROM t2 NATURAL JOIN t1;
2 3 4
3 4 5

query III rowsort
SELECT /*+ HASH_JOIN */ t2.* FROM t2 NATURAL JOIN t1;
----
2 3 4
3 4 5

query III rowsort
SELECT t1.* FROM t2 NATURAL JOIN t1;
----