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

alexpl pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new 381e9be96fe IGNITE-25875 SQL Calcite: Refactor PlannerTest - Fixes 
#12180.
381e9be96fe is described below

commit 381e9be96fe1d29bc4791c27571fd5a75bfd2b40
Author: Aleksey Plekhanov <[email protected]>
AuthorDate: Mon Jul 14 18:43:39 2025 +0500

    IGNITE-25875 SQL Calcite: Refactor PlannerTest - Fixes #12180.
    
    Signed-off-by: Aleksey Plekhanov <[email protected]>
---
 .../CorrelatedNestedLoopJoinPlannerTest.java       |  35 +
 .../calcite/planner/MergeJoinPlannerTest.java      |  22 +
 .../query/calcite/planner/PlanExecutionTest.java   | 361 +++++++++
 .../query/calcite/planner/PlanSplitterTest.java    | 281 +++++++
 .../query/calcite/planner/PlannerTest.java         | 855 ---------------------
 .../planner/ProjectFilterScanMergePlannerTest.java |  12 +
 .../query/calcite/planner/TestTable.java           |  18 +
 .../apache/ignite/testsuites/PlannerTestSuite.java |   6 +-
 8 files changed, 733 insertions(+), 857 deletions(-)

diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/CorrelatedNestedLoopJoinPlannerTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/CorrelatedNestedLoopJoinPlannerTest.java
index fa9f5f2b44b..84047593d7b 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/CorrelatedNestedLoopJoinPlannerTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/CorrelatedNestedLoopJoinPlannerTest.java
@@ -22,7 +22,9 @@ import java.util.function.Predicate;
 
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.JoinRelType;
 import org.apache.calcite.rex.RexFieldAccess;
+import org.apache.calcite.util.ImmutableBitSet;
 import 
org.apache.ignite.internal.processors.query.calcite.prepare.bounds.ExactBounds;
 import 
org.apache.ignite.internal.processors.query.calcite.prepare.bounds.SearchBounds;
 import 
org.apache.ignite.internal.processors.query.calcite.rel.IgniteCorrelatedNestedLoopJoin;
@@ -33,6 +35,9 @@ import 
org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
 import 
org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
 import org.junit.Test;
 
+import static org.apache.calcite.sql.type.SqlTypeName.INTEGER;
+import static org.apache.calcite.sql.type.SqlTypeName.VARCHAR;
+
 /**
  *
  */
@@ -120,6 +125,36 @@ public class CorrelatedNestedLoopJoinPlannerTest extends 
AbstractPlannerTest {
         assertPlan(sql, publicSchema, check);
     }
 
+    /**
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testJoinPushExpressionRule() throws Exception {
+        IgniteSchema publicSchema = createSchema(
+            createTable("EMP", 100, IgniteDistributions.broadcast(),
+                "ID", INTEGER, "NAME", VARCHAR, "DEPTNO", INTEGER),
+            createTable("DEPT", 10, IgniteDistributions.broadcast(),
+                "DEPTNO", INTEGER, "NAME", VARCHAR)
+        );
+
+        String sql = "select /*+ CNL_JOIN */ d.deptno, e.deptno " +
+            "from dept d, emp e " +
+            "where d.deptno + e.deptno = 2";
+
+        assertPlan(sql, publicSchema, 
isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)
+            .and(cnlj -> "=(+($0, $1), 
2)".equals(cnlj.getCondition().toString()))
+            .and(cnlj -> cnlj.getJoinType() == JoinRelType.INNER)
+            .and(cnlj -> cnlj.getVariablesSet().size() == 1)
+            .and(input(0, isTableScan("DEPT")
+                .and(t -> t.requiredColumns().equals(ImmutableBitSet.of(0)))
+            ))
+            .and(input(1, isTableScan("EMP")
+                .and(t -> t.requiredColumns().equals(ImmutableBitSet.of(2)))
+                .and(t -> "=(+($cor1.DEPTNO, $t0), 
2)".equals(t.condition().toString()))
+            ))
+        );
+    }
+
     /** */
     private TestTable testTable(String name) {
         return createTable(name, IgniteDistributions.broadcast(), "ID", 
Integer.class, "JID", Integer.class, "VAL", String.class);
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/MergeJoinPlannerTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/MergeJoinPlannerTest.java
index 208387f7a70..97bb8425f1f 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/MergeJoinPlannerTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/MergeJoinPlannerTest.java
@@ -34,6 +34,8 @@ import org.junit.Test;
 
 import static org.apache.calcite.rel.RelFieldCollation.Direction.ASCENDING;
 import static org.apache.calcite.rel.RelFieldCollation.Direction.DESCENDING;
+import static org.apache.calcite.sql.type.SqlTypeName.INTEGER;
+import static org.apache.calcite.sql.type.SqlTypeName.VARCHAR;
 
 /** MergeJoin planner test. */
 public class MergeJoinPlannerTest extends AbstractPlannerTest {
@@ -2794,6 +2796,26 @@ public class MergeJoinPlannerTest extends 
AbstractPlannerTest {
         assertNull(sortOnTopOfScan(rel, "RIGHT_T"));
     }
 
+    /** */
+    @Test
+    public void testMergeJoinIsNotAppliedForNonEquiJoin() throws Exception {
+        IgniteSchema schema = createSchema(
+            createTable("EMP", 1000, IgniteDistributions.broadcast(),
+                "ID", INTEGER, "NAME", VARCHAR, "DEPTNO", INTEGER)
+                .addIndex("emp_idx", 1, 2),
+            createTable("DEPT", 100, IgniteDistributions.broadcast(),
+                "DEPTNO", INTEGER, "NAME", VARCHAR)
+                .addIndex("dep_idx", 1, 0)
+        );
+
+        String sql = "select d.deptno, d.name, e.id, e.name from dept d join 
emp e " +
+            "on d.deptno = e.deptno and e.name >= d.name order by e.name, 
d.deptno";
+
+        assertPlan(sql, schema, nodeOrAnyChild(isInstanceOf(IgniteSort.class)
+                
.and(hasChildThat(isInstanceOf(IgniteMergeJoin.class)).negate())),
+            "CorrelatedNestedLoopJoin");
+    }
+
     /**
      * Search for sort node whose input is arbitrary scan node.
      *
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlanExecutionTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlanExecutionTest.java
new file mode 100644
index 00000000000..40a803dc19b
--- /dev/null
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlanExecutionTest.java
@@ -0,0 +1,361 @@
+/*
+ * 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.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.schema.SchemaPlus;
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.internal.processors.cache.GridCacheProcessor;
+import org.apache.ignite.internal.processors.query.calcite.QueryRegistryImpl;
+import 
org.apache.ignite.internal.processors.query.calcite.exec.ArrayRowHandler;
+import 
org.apache.ignite.internal.processors.query.calcite.exec.ExchangeServiceImpl;
+import 
org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
+import 
org.apache.ignite.internal.processors.query.calcite.exec.LogicalRelImplementor;
+import 
org.apache.ignite.internal.processors.query.calcite.exec.MailboxRegistryImpl;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.Node;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.Outbox;
+import org.apache.ignite.internal.processors.query.calcite.exec.rel.RootNode;
+import 
org.apache.ignite.internal.processors.query.calcite.exec.task.StripedQueryTaskExecutor;
+import 
org.apache.ignite.internal.processors.query.calcite.exec.tracker.NoOpIoTracker;
+import 
org.apache.ignite.internal.processors.query.calcite.exec.tracker.NoOpMemoryTracker;
+import 
org.apache.ignite.internal.processors.query.calcite.message.MessageServiceImpl;
+import 
org.apache.ignite.internal.processors.query.calcite.message.TestIoManager;
+import 
org.apache.ignite.internal.processors.query.calcite.metadata.ColocationGroup;
+import 
org.apache.ignite.internal.processors.query.calcite.metadata.FragmentDescription;
+import 
org.apache.ignite.internal.processors.query.calcite.prepare.BaseQueryContext;
+import 
org.apache.ignite.internal.processors.query.calcite.prepare.ExecutionPlan;
+import org.apache.ignite.internal.processors.query.calcite.prepare.Fragment;
+import 
org.apache.ignite.internal.processors.query.calcite.prepare.MappingQueryContext;
+import 
org.apache.ignite.internal.processors.query.calcite.prepare.MultiStepPlan;
+import 
org.apache.ignite.internal.processors.query.calcite.prepare.MultiStepQueryPlan;
+import 
org.apache.ignite.internal.processors.query.calcite.prepare.PlanningContext;
+import 
org.apache.ignite.internal.processors.query.calcite.prepare.QueryTemplate;
+import org.apache.ignite.internal.processors.query.calcite.prepare.Splitter;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
+import 
org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
+import 
org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
+import 
org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
+import 
org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeSystem;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import 
org.apache.ignite.internal.processors.security.NoOpIgniteSecurityProcessor;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.testframework.junits.GridTestKernalContext;
+import org.apache.ignite.thread.IgniteStripedThreadPoolExecutor;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.apache.calcite.tools.Frameworks.createRootSchema;
+import static 
org.apache.ignite.configuration.IgniteConfiguration.DFLT_THREAD_KEEP_ALIVE_TIME;
+
+/**
+ *
+ */
+@SuppressWarnings({"TooBroadScope", "FieldCanBeLocal", "TypeMayBeWeakened"})
+public class PlanExecutionTest extends AbstractPlannerTest {
+    /**
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testPhysicalPlan() throws Exception {
+        executors = new ArrayList<>();
+
+        IgniteTypeFactory f = new IgniteTypeFactory(IgniteTypeSystem.INSTANCE);
+
+        TestTable developer = new TestTable(
+            new RelDataTypeFactory.Builder(f)
+                .add("ID", f.createJavaType(Integer.class))
+                .add("NAME", f.createJavaType(String.class))
+                .add("PROJECTID", f.createJavaType(Integer.class))
+                .build()) {
+            @Override public <Row> Iterable<Row> scan(
+                ExecutionContext<Row> execCtx,
+                ColocationGroup grp,
+                ImmutableBitSet requiredColumns
+            ) {
+                return Arrays.asList(
+                    row(execCtx, requiredColumns, 0, "Igor", 0),
+                    row(execCtx, requiredColumns, 1, "Roman", 0)
+                );
+            }
+
+            @Override public ColocationGroup 
colocationGroup(MappingQueryContext ctx) {
+                return ColocationGroup.forNodes(select(nodes, 1));
+            }
+
+            @Override public IgniteDistribution distribution() {
+                return IgniteDistributions.broadcast();
+            }
+        };
+
+        TestTable project = new TestTable(
+            new RelDataTypeFactory.Builder(f)
+                .add("ID", f.createJavaType(Integer.class))
+                .add("NAME", f.createJavaType(String.class))
+                .add("VER", f.createJavaType(Integer.class))
+                .build()) {
+            @Override public <Row> Iterable<Row> scan(
+                ExecutionContext<Row> execCtx,
+                ColocationGroup grp,
+                ImmutableBitSet requiredColumns
+            ) {
+                return Arrays.asList(
+                    row(execCtx, requiredColumns, 0, "Calcite", 1),
+                    row(execCtx, requiredColumns, 1, "Ignite", 1)
+                );
+            }
+
+            @Override public ColocationGroup 
colocationGroup(MappingQueryContext ctx) {
+                return ColocationGroup.forNodes(select(nodes, 1));
+            }
+
+            @Override public IgniteDistribution distribution() {
+                return IgniteDistributions.broadcast();
+            }
+        };
+
+        IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
+
+        publicSchema.addTable("DEVELOPER", developer);
+        publicSchema.addTable("PROJECT", project);
+
+        String sql = "SELECT d.id, d.name, d.projectId, p.name0, p.ver0 " +
+            "FROM PUBLIC.Developer d JOIN (" +
+            "SELECT pp.id as id0, pp.name as name0, pp.ver as ver0 FROM 
PUBLIC.Project pp" +
+            ") p " +
+            "ON d.projectId = p.id0 " +
+            "WHERE (d.projectId + 1) > ?";
+
+        List<Object[]> res = executeQuery(publicSchema, sql, -10);
+
+        assertFalse(res.isEmpty());
+
+        Assert.assertArrayEquals(new Object[]{0, "Igor", 0, "Calcite", 1}, 
res.get(0));
+        Assert.assertArrayEquals(new Object[]{1, "Roman", 0, "Calcite", 1}, 
res.get(1));
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testPhysicalPlan2() throws Exception {
+        executors = new ArrayList<>();
+
+        IgniteTypeFactory f = new IgniteTypeFactory(IgniteTypeSystem.INSTANCE);
+
+        ThreadLocal<List<?>> checkRes = new ThreadLocal<>();
+
+        TestTable testTbl = new TestTable(
+            new RelDataTypeFactory.Builder(f)
+                .add("ID0", f.createJavaType(Integer.class))
+                .add("ID1", f.createJavaType(Integer.class))
+                .build()) {
+            @Override public <Row> Iterable<Row> scan(
+                ExecutionContext<Row> execCtx,
+                ColocationGroup grp,
+                ImmutableBitSet requiredColumns
+            ) {
+                List<Row> res = new ArrayList<>();
+                List<Row> checkRes0 = new ArrayList<>();
+
+                for (int i = 0; i < 10; ++i) {
+                    int col = ThreadLocalRandom.current().nextInt(1_000);
+
+                    res.add(row(execCtx, requiredColumns, col, col));
+                    checkRes0.add(row(execCtx, null, col + col));
+                }
+
+                checkRes.set(checkRes0);
+
+                return res;
+            }
+
+            @Override public ColocationGroup 
colocationGroup(MappingQueryContext ctx) {
+                return ColocationGroup.forNodes(select(nodes, 1));
+            }
+
+            @Override public IgniteDistribution distribution() {
+                return IgniteDistributions.broadcast();
+            }
+        };
+
+        IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
+
+        publicSchema.addTable("TEST_TABLE", testTbl);
+
+        String sql = "SELECT (ID0 + ID1) AS RES FROM PUBLIC.TEST_TABLE";
+
+        List<Object[]> res = executeQuery(publicSchema, sql);
+
+        assertFalse(res.isEmpty());
+
+        int pos = 0;
+
+        for (Object obj : checkRes.get())
+            Assert.assertArrayEquals((Object[])obj, res.get(pos++));
+    }
+
+    /** */
+    private List<Object[]> executeQuery(IgniteSchema publicSchema, String sql, 
Object... parameters) throws Exception {
+        SchemaPlus schema = createRootSchema(false)
+            .add("PUBLIC", publicSchema);
+
+        BaseQueryContext qctx = BaseQueryContext.builder()
+            .logger(log)
+            .defaultSchema(schema)
+            .build();
+
+        PlanningContext ctx = PlanningContext.builder()
+            .parentContext(qctx)
+            .query(sql)
+            .parameters(parameters)
+            .build();
+
+        IgniteRel phys = physicalPlan(ctx);
+
+        ExecutionPlan plan = splitPlan(phys);
+
+        List<Fragment> fragments = plan.fragments();
+        assertEquals(2, fragments.size());
+
+        UUID qryId = UUID.randomUUID();
+
+        TestIoManager mgr = new TestIoManager();
+        Node<Object[]> exec;
+
+        //// Local part
+
+        Fragment fragment = fragments.get(0);
+        assert fragment.rootFragment();
+
+        exec = implementFragment(qctx, ctx, mgr, plan, fragment, qryId, 
F.first(nodes));
+
+        RootNode<Object[]> consumer = new RootNode<>(exec.context(), 
exec.rowType());
+        consumer.register(exec);
+
+        //// Remote part
+
+        fragment = fragments.get(1);
+
+        assert !fragment.rootFragment();
+
+        exec = implementFragment(qctx, ctx, mgr, plan, fragment, qryId, 
nodes.get(1));
+
+        //// Start execution
+
+        assert exec instanceof Outbox;
+
+        Outbox<Object[]> outbox = (Outbox<Object[]>)exec;
+
+        exec.context().execute(outbox::init, outbox::onError);
+
+        List<Object[]> res = new ArrayList<>();
+
+        while (consumer.hasNext())
+            res.add(consumer.next());
+
+        return res;
+    }
+
+    /** */
+    private ExecutionPlan splitPlan(IgniteRel phys) {
+        assertNotNull(phys);
+
+        MultiStepPlan plan = new MultiStepQueryPlan(null, null, new 
QueryTemplate(new Splitter().go(phys)), null, null);
+
+        assertNotNull(plan);
+
+        return plan.init(this::intermediateMapping, null, 
Commons.mapContext(F.first(nodes), AffinityTopologyVersion.NONE));
+    }
+
+    /**
+     * Transforms fragment to the execution node.
+     */
+    private Node<Object[]> implementFragment(
+        BaseQueryContext qctx,
+        PlanningContext ctx,
+        TestIoManager mgr,
+        ExecutionPlan plan,
+        Fragment fragment,
+        UUID qryId,
+        UUID nodeId
+    ) throws IgniteCheckedException {
+        GridTestKernalContext kernal = newContext();
+        kernal.add(new NoOpIgniteSecurityProcessor(kernal));
+        kernal.add(new GridCacheProcessor(kernal));
+
+        StripedQueryTaskExecutor taskExecutor = new 
StripedQueryTaskExecutor(kernal);
+        taskExecutor.stripedThreadPoolExecutor(new 
IgniteStripedThreadPoolExecutor(
+            kernal.config().getQueryThreadPoolSize(),
+            kernal.igniteInstanceName(),
+            "calciteQry",
+            (t, ex) -> {
+                log().error(ex.getMessage(), ex);
+                lastE = ex;
+            },
+            true,
+            DFLT_THREAD_KEEP_ALIVE_TIME
+        ));
+        executors.add(taskExecutor);
+
+        MessageServiceImpl msgSvc = new TestMessageServiceImpl(kernal, mgr);
+
+        msgSvc.localNodeId(nodeId);
+        msgSvc.taskExecutor(taskExecutor);
+        mgr.register(msgSvc);
+
+        MailboxRegistryImpl mailboxRegistry = new MailboxRegistryImpl(kernal);
+
+        ExchangeServiceImpl exchangeSvc = new ExchangeServiceImpl(kernal);
+        exchangeSvc.taskExecutor(taskExecutor);
+        exchangeSvc.messageService(msgSvc);
+        exchangeSvc.mailboxRegistry(mailboxRegistry);
+        exchangeSvc.queryRegistry(new QueryRegistryImpl(kernal));
+        exchangeSvc.init();
+
+        ExecutionContext<Object[]> ectx = new ExecutionContext<>(
+            qctx,
+            taskExecutor,
+            null,
+            qryId,
+            nodeId,
+            F.first(nodes),
+            AffinityTopologyVersion.NONE,
+            new FragmentDescription(
+                fragment.fragmentId(),
+                fragment.mapping(),
+                plan.target(fragment),
+                plan.remotes(fragment)),
+            ArrayRowHandler.INSTANCE,
+            NoOpMemoryTracker.INSTANCE,
+            NoOpIoTracker.INSTANCE,
+            0,
+            Commons.parametersMap(ctx.parameters()),
+            null);
+
+        return new LogicalRelImplementor<>(ectx, c -> r -> 0, mailboxRegistry, 
exchangeSvc,
+            new TestFailureProcessor(kernal)).go(fragment.root());
+    }
+}
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlanSplitterTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlanSplitterTest.java
new file mode 100644
index 00000000000..72cf2738a51
--- /dev/null
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlanSplitterTest.java
@@ -0,0 +1,281 @@
+/*
+ * 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.Arrays;
+import java.util.function.Predicate;
+import org.apache.calcite.rel.core.Exchange;
+import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import 
org.apache.ignite.internal.processors.query.calcite.metadata.ColocationGroup;
+import 
org.apache.ignite.internal.processors.query.calcite.prepare.ExecutionPlan;
+import 
org.apache.ignite.internal.processors.query.calcite.prepare.MultiStepPlan;
+import 
org.apache.ignite.internal.processors.query.calcite.prepare.MultiStepQueryPlan;
+import 
org.apache.ignite.internal.processors.query.calcite.prepare.QueryTemplate;
+import org.apache.ignite.internal.processors.query.calcite.prepare.Splitter;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
+import 
org.apache.ignite.internal.processors.query.calcite.rel.IgniteTrimExchange;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
+import 
org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
+import 
org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.internal.util.typedef.F;
+import org.junit.Test;
+
+import static org.apache.calcite.sql.type.SqlTypeName.INTEGER;
+import static org.apache.calcite.sql.type.SqlTypeName.VARCHAR;
+
+/** */
+public class PlanSplitterTest extends AbstractPlannerTest {
+    /** */
+    private IgniteSchema createSchema(
+        IgniteDistribution distribution1,
+        ColocationGroup colocationGrp1,
+        IgniteDistribution distribution2,
+        ColocationGroup colocationGrp2
+    ) {
+        return createSchema(
+            createTable("DEVELOPER", distribution1, "ID", INTEGER, "NAME", 
VARCHAR, "PROJECTID", INTEGER)
+                .setColocationGroup(colocationGrp1),
+            createTable("PROJECT", distribution2, "ID", INTEGER, "NAME", 
VARCHAR, "VER", INTEGER)
+                .setColocationGroup(colocationGrp2)
+        );
+    }
+
+    /** */
+    @Test
+    public void testSplitterColocatedPartitionedPartitioned() throws Exception 
{
+        IgniteSchema schema = createSchema(
+            IgniteDistributions.affinity(0, "Developer", "hash"),
+            ColocationGroup.forAssignments(Arrays.asList(
+                select(nodes, 0, 1),
+                select(nodes, 1, 2),
+                select(nodes, 2, 0),
+                select(nodes, 0, 1),
+                select(nodes, 1, 2)
+            )),
+            IgniteDistributions.affinity(0, "Project", "hash"),
+            ColocationGroup.forAssignments(Arrays.asList(
+                select(nodes, 0, 1),
+                select(nodes, 1, 2),
+                select(nodes, 2, 0),
+                select(nodes, 0, 1),
+                select(nodes, 1, 2)
+            ))
+        );
+
+        String sql = "SELECT d.id, d.name, d.projectId, p.id0, p.ver0 " +
+            "FROM PUBLIC.Developer d JOIN (" +
+            "SELECT pp.id as id0, pp.ver as ver0 FROM PUBLIC.Project pp" +
+            ") p " +
+            "ON d.id = p.id0";
+
+        // Data is partitioned and colocated, can be joined on remote nodes, 
one exchange is required to transfer to
+        // initiator node.
+        assertPlan(sql, schema, 
hasFragmentsCount(2).and(nodeOrAnyChild(isInstanceOf(Exchange.class))));
+    }
+
+    /** */
+    @Test
+    public void testSplitterColocatedReplicatedReplicated() throws Exception {
+        IgniteSchema schema = createSchema(
+            IgniteDistributions.broadcast(),
+            ColocationGroup.forNodes(select(nodes, 0, 1, 2, 3)),
+            IgniteDistributions.broadcast(),
+            ColocationGroup.forNodes(select(nodes, 0, 1, 2, 3))
+        );
+
+        String sql = "SELECT d.id, (d.id + 1) as id2, d.name, d.projectId, 
p.id0, p.ver0 " +
+            "FROM PUBLIC.Developer d JOIN (" +
+            "SELECT pp.id as id0, pp.ver as ver0 FROM PUBLIC.Project pp" +
+            ") p " +
+            "ON d.id = p.id0 " +
+            "WHERE (d.projectId + 1) = ?";
+
+        // Data is fully replicated, no exchanges requred, can be executed 
direcly on initiatpr node.
+        assertPlan(sql, schema, 
hasFragmentsCount(1).and(nodeOrAnyChild(isInstanceOf(Exchange.class)).negate()));
+    }
+
+    /** */
+    @Test
+    public void testSplitterPartiallyColocatedReplicatedAndPartitioned() 
throws Exception {
+        IgniteSchema schema = createSchema(
+            IgniteDistributions.broadcast(),
+            ColocationGroup.forNodes(select(nodes, 0)),
+            IgniteDistributions.affinity(0, "Project", "hash"),
+            ColocationGroup.forAssignments(Arrays.asList(
+                select(nodes, 1, 2),
+                select(nodes, 2, 3),
+                select(nodes, 3, 0),
+                select(nodes, 0, 1)
+            ))
+        );
+
+        String sql = "SELECT d.id, d.name, d.projectId, p.id0, p.ver0 " +
+            "FROM PUBLIC.Developer d JOIN (" +
+            "SELECT pp.id as id0, pp.ver as ver0 FROM PUBLIC.Project pp" +
+            ") p " +
+            "ON d.id = p.id0 " +
+            "WHERE (d.projectId + 1) = ?";
+
+        // First table is replicated and planned with TrimExchange to colocate 
data, but set of nodes for partitioned
+        // table is differ, so exchange is added after fragments split for 
colocation. Another exchange is added after
+        // fragments split to send data to initiator node.
+        assertPlan(sql, schema, 
hasFragmentsCount(3).and(hasChildThat(isInstanceOf(IgniteTrimExchange.class))));
+    }
+
+    /** */
+    @Test
+    public void testSplitterPartiallyColocated1() throws Exception {
+        IgniteSchema schema = createSchema(
+            IgniteDistributions.broadcast(),
+            ColocationGroup.forNodes(select(nodes, 1, 2, 3)),
+            IgniteDistributions.affinity(0, "Project", "hash"),
+            ColocationGroup.forAssignments(Arrays.asList(
+                select(nodes, 0),
+                select(nodes, 1),
+                select(nodes, 2)
+            ))
+        );
+
+        String sql = "SELECT d.id, d.name, d.projectId, p.id0, p.ver0 " +
+            "FROM PUBLIC.Developer d JOIN (" +
+            "SELECT pp.id as id0, pp.ver as ver0 FROM PUBLIC.Project pp" +
+            ") p " +
+            "ON d.projectId = p.id0 " +
+            "WHERE (d.projectId + 1) = ?";
+
+        // First table is replicated and planned with TrimExchange to colocate 
data, but set of nodes for partitioned
+        // table is differ, so exchange is added after fragments split for 
colocation. Another exchange is added after
+        // fragments split to send data to initiator node.
+        assertPlan(sql, schema, 
hasFragmentsCount(3).and(hasChildThat(isInstanceOf(IgniteTrimExchange.class))));
+    }
+
+    /** */
+    @Test
+    public void testSplitterPartiallyColocated2() throws Exception {
+        IgniteSchema schema = createSchema(
+            IgniteDistributions.broadcast(),
+            ColocationGroup.forNodes(select(nodes, 0)),
+            IgniteDistributions.affinity(0, "Project", "hash"),
+            ColocationGroup.forAssignments(Arrays.asList(
+                select(nodes, 1),
+                select(nodes, 2),
+                select(nodes, 3)
+            ))
+        );
+
+        String sql = "SELECT d.id, d.name, d.projectId, p.id0, p.ver0 " +
+            "FROM PUBLIC.Developer d JOIN (" +
+            "SELECT pp.id as id0, pp.ver as ver0 FROM PUBLIC.Project pp" +
+            ") p " +
+            "ON d.projectId = p.id0 " +
+            "WHERE (d.projectId + 1) = ?";
+
+        // First table is replicated and planned with TrimExchange to colocate 
data, but set of nodes for partitioned
+        // table is differ, so exchange is added after fragments split for 
colocation. Another exchange is added after
+        // fragments split to send data to initiator node.
+        assertPlan(sql, schema, 
hasFragmentsCount(3).and(hasChildThat(isInstanceOf(IgniteTrimExchange.class))));
+    }
+
+    /** */
+    @Test
+    public void testSplitterNonColocatedReplicatedReplicated1() throws 
Exception {
+        IgniteSchema schema = createSchema(
+            IgniteDistributions.broadcast(),
+            ColocationGroup.forNodes(select(nodes, 2)),
+            IgniteDistributions.broadcast(),
+            ColocationGroup.forNodes(select(nodes, 0, 1))
+        );
+
+        String sql = "SELECT p.id0, d.id " +
+            "FROM PUBLIC.Developer d JOIN (" +
+            "SELECT pp.id as id0, pp.ver as ver0 FROM PUBLIC.Project pp" +
+            ") p " +
+            "ON d.projectId = p.ver0 " +
+            "WHERE (d.projectId + 1) = ?";
+
+        // Originally planned without exchanges. But there is no data for 
table 1 on initiator node, so one exchange
+        // is added after fragments split.
+        assertPlan(sql, schema, 
hasFragmentsCount(2).and(nodeOrAnyChild(isInstanceOf(Exchange.class)).negate()));
+    }
+
+    /** */
+    @Test
+    public void testSplitterNonColocatedReplicatedReplicated2() throws 
Exception {
+        IgniteSchema schema = createSchema(
+            IgniteDistributions.broadcast(),
+            ColocationGroup.forNodes(select(nodes, 2)),
+            IgniteDistributions.broadcast(),
+            ColocationGroup.forNodes(select(nodes, 1))
+        );
+
+        String sql = "SELECT p.id0, d.id " +
+            "FROM PUBLIC.Developer d JOIN (" +
+            "SELECT pp.id as id0, pp.ver as ver0 FROM PUBLIC.Project pp" +
+            ") p " +
+            "ON d.projectId = p.ver0 " +
+            "WHERE (d.projectId + 1) = ?";
+
+        // Originally planned without exchanges. But data can't be joined on 
any single node, so two exchanges is
+        // added after fragments split.
+        assertPlan(sql, schema, 
hasFragmentsCount(3).and(nodeOrAnyChild(isInstanceOf(Exchange.class)).negate()));
+    }
+
+    /** */
+    @Test
+    public void testSplitterPartiallyColocatedReplicatedReplicated() throws 
Exception {
+        IgniteSchema schema = createSchema(
+            IgniteDistributions.broadcast(),
+            ColocationGroup.forNodes(select(nodes, 2, 3)),
+            IgniteDistributions.broadcast(),
+            ColocationGroup.forNodes(select(nodes, 1, 3))
+        );
+
+        String sql = "SELECT p.id0, d.id " +
+            "FROM PUBLIC.Developer d JOIN (" +
+            "SELECT pp.id as id0, pp.ver as ver0 FROM PUBLIC.Project pp" +
+            ") p " +
+            "ON d.projectId = p.ver0 " +
+            "WHERE (d.projectId + 1) = ?";
+
+        // Originally planned without exchanges. There is no data on initiator 
node, but data can be joined on one
+        // remote node, so one exchange is added after fragments split.
+        assertPlan(sql, schema, 
hasFragmentsCount(2).and(nodeOrAnyChild(isInstanceOf(Exchange.class)).negate()));
+    }
+
+    /**
+     * Predicate builder for "Fragments count" condition.
+     */
+    protected <T extends IgniteRel> Predicate<T> hasFragmentsCount(int cnt) {
+        return node -> {
+            MultiStepPlan plan = new MultiStepQueryPlan(null, null, new 
QueryTemplate(new Splitter().go(node)),
+                null, null);
+
+            ExecutionPlan execPlan = plan.init(this::intermediateMapping, null,
+                Commons.mapContext(F.first(nodes), 
AffinityTopologyVersion.NONE));
+
+            if (execPlan.fragments().size() != cnt) {
+                lastErrorMsg = "Unexpected fragments count [expected=" + cnt + 
", actual=" + execPlan.fragments().size() + ']';
+
+                return false;
+            }
+
+            return true;
+        };
+    }
+}
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlannerTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlannerTest.java
deleted file mode 100644
index 1627da8b900..00000000000
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/PlannerTest.java
+++ /dev/null
@@ -1,855 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.processors.query.calcite.planner;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.ThreadLocalRandom;
-import java.util.concurrent.atomic.AtomicInteger;
-import org.apache.calcite.plan.RelOptUtil;
-import org.apache.calcite.rel.RelNode;
-import org.apache.calcite.rel.RelVisitor;
-import org.apache.calcite.rel.core.JoinRelType;
-import org.apache.calcite.rel.type.RelDataTypeFactory;
-import org.apache.calcite.schema.SchemaPlus;
-import org.apache.calcite.util.ImmutableBitSet;
-import org.apache.ignite.IgniteCheckedException;
-import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
-import org.apache.ignite.internal.processors.cache.GridCacheProcessor;
-import org.apache.ignite.internal.processors.query.calcite.QueryRegistryImpl;
-import 
org.apache.ignite.internal.processors.query.calcite.exec.ArrayRowHandler;
-import 
org.apache.ignite.internal.processors.query.calcite.exec.ExchangeServiceImpl;
-import 
org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
-import 
org.apache.ignite.internal.processors.query.calcite.exec.LogicalRelImplementor;
-import 
org.apache.ignite.internal.processors.query.calcite.exec.MailboxRegistryImpl;
-import org.apache.ignite.internal.processors.query.calcite.exec.rel.Node;
-import org.apache.ignite.internal.processors.query.calcite.exec.rel.Outbox;
-import org.apache.ignite.internal.processors.query.calcite.exec.rel.RootNode;
-import 
org.apache.ignite.internal.processors.query.calcite.exec.task.StripedQueryTaskExecutor;
-import 
org.apache.ignite.internal.processors.query.calcite.exec.tracker.NoOpIoTracker;
-import 
org.apache.ignite.internal.processors.query.calcite.exec.tracker.NoOpMemoryTracker;
-import 
org.apache.ignite.internal.processors.query.calcite.message.MessageServiceImpl;
-import 
org.apache.ignite.internal.processors.query.calcite.message.TestIoManager;
-import 
org.apache.ignite.internal.processors.query.calcite.metadata.ColocationGroup;
-import 
org.apache.ignite.internal.processors.query.calcite.metadata.FragmentDescription;
-import 
org.apache.ignite.internal.processors.query.calcite.prepare.BaseQueryContext;
-import 
org.apache.ignite.internal.processors.query.calcite.prepare.ExecutionPlan;
-import org.apache.ignite.internal.processors.query.calcite.prepare.Fragment;
-import 
org.apache.ignite.internal.processors.query.calcite.prepare.MappingQueryContext;
-import 
org.apache.ignite.internal.processors.query.calcite.prepare.MultiStepPlan;
-import 
org.apache.ignite.internal.processors.query.calcite.prepare.MultiStepQueryPlan;
-import 
org.apache.ignite.internal.processors.query.calcite.prepare.PlanningContext;
-import 
org.apache.ignite.internal.processors.query.calcite.prepare.QueryTemplate;
-import org.apache.ignite.internal.processors.query.calcite.prepare.Splitter;
-import 
org.apache.ignite.internal.processors.query.calcite.rel.IgniteCorrelatedNestedLoopJoin;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteFilter;
-import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
-import org.apache.ignite.internal.processors.query.calcite.schema.IgniteSchema;
-import 
org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
-import 
org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions;
-import 
org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
-import 
org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeSystem;
-import org.apache.ignite.internal.processors.query.calcite.util.Commons;
-import 
org.apache.ignite.internal.processors.security.NoOpIgniteSecurityProcessor;
-import org.apache.ignite.internal.util.typedef.F;
-import org.apache.ignite.testframework.junits.GridTestKernalContext;
-import org.apache.ignite.thread.IgniteStripedThreadPoolExecutor;
-import org.junit.Assert;
-import org.junit.Test;
-
-import static org.apache.calcite.sql.type.SqlTypeName.INTEGER;
-import static org.apache.calcite.sql.type.SqlTypeName.VARCHAR;
-import static org.apache.calcite.tools.Frameworks.createRootSchema;
-import static 
org.apache.ignite.configuration.IgniteConfiguration.DFLT_THREAD_KEEP_ALIVE_TIME;
-
-/**
- *
- */
-//@WithSystemProperty(key = "calcite.debug", value = "true")
-@SuppressWarnings({"TooBroadScope", "FieldCanBeLocal", "TypeMayBeWeakened"})
-public class PlannerTest extends AbstractPlannerTest {
-    /**
-     * @throws Exception If failed.
-     */
-    @Test
-    public void testSplitterColocatedPartitionedPartitioned() throws Exception 
{
-        IgniteTypeFactory f = new IgniteTypeFactory(IgniteTypeSystem.INSTANCE);
-
-        TestTable developer = new TestTable(
-            new RelDataTypeFactory.Builder(f)
-                .add("ID", f.createJavaType(Integer.class))
-                .add("NAME", f.createJavaType(String.class))
-                .add("PROJECTID", f.createJavaType(Integer.class))
-                .build()) {
-            @Override public ColocationGroup 
colocationGroup(MappingQueryContext ctx) {
-                return ColocationGroup.forAssignments(Arrays.asList(
-                    select(nodes, 0, 1),
-                    select(nodes, 1, 2),
-                    select(nodes, 2, 0),
-                    select(nodes, 0, 1),
-                    select(nodes, 1, 2)
-                ));
-            }
-
-            @Override public IgniteDistribution distribution() {
-                return IgniteDistributions.affinity(0, "Developer", "hash");
-            }
-        };
-
-        TestTable project = new TestTable(
-            new RelDataTypeFactory.Builder(f)
-                .add("ID", f.createJavaType(Integer.class))
-                .add("NAME", f.createJavaType(String.class))
-                .add("VER", f.createJavaType(Integer.class))
-                .build()) {
-            @Override public ColocationGroup 
colocationGroup(MappingQueryContext ctx) {
-                return ColocationGroup.forAssignments(Arrays.asList(
-                    select(nodes, 0, 1),
-                    select(nodes, 1, 2),
-                    select(nodes, 2, 0),
-                    select(nodes, 0, 1),
-                    select(nodes, 1, 2)));
-            }
-
-            @Override public IgniteDistribution distribution() {
-                return IgniteDistributions.affinity(0, "Project", "hash");
-            }
-        };
-
-        IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
-
-        publicSchema.addTable("DEVELOPER", developer);
-        publicSchema.addTable("PROJECT", project);
-
-        String sql = "SELECT d.id, d.name, d.projectId, p.id0, p.ver0 " +
-            "FROM PUBLIC.Developer d JOIN (" +
-            "SELECT pp.id as id0, pp.ver as ver0 FROM PUBLIC.Project pp" +
-            ") p " +
-            "ON d.id = p.id0";
-
-        IgniteRel phys = physicalPlan(sql, publicSchema);
-
-        assertEquals(2, splitPlan(phys).fragments().size());
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    @Test
-    public void testPhysicalPlan() throws Exception {
-        executors = new ArrayList<>();
-
-        IgniteTypeFactory f = new IgniteTypeFactory(IgniteTypeSystem.INSTANCE);
-
-        TestTable developer = new TestTable(
-            new RelDataTypeFactory.Builder(f)
-                .add("ID", f.createJavaType(Integer.class))
-                .add("NAME", f.createJavaType(String.class))
-                .add("PROJECTID", f.createJavaType(Integer.class))
-                .build()) {
-            @Override public <Row> Iterable<Row> scan(
-                ExecutionContext<Row> execCtx,
-                ColocationGroup grp,
-                ImmutableBitSet requiredColumns
-            ) {
-                return Arrays.asList(
-                    row(execCtx, requiredColumns, 0, "Igor", 0),
-                    row(execCtx, requiredColumns, 1, "Roman", 0)
-                );
-            }
-
-            @Override public ColocationGroup 
colocationGroup(MappingQueryContext ctx) {
-                return ColocationGroup.forNodes(select(nodes, 1));
-            }
-
-            @Override public IgniteDistribution distribution() {
-                return IgniteDistributions.broadcast();
-            }
-        };
-
-        TestTable project = new TestTable(
-            new RelDataTypeFactory.Builder(f)
-                .add("ID", f.createJavaType(Integer.class))
-                .add("NAME", f.createJavaType(String.class))
-                .add("VER", f.createJavaType(Integer.class))
-                .build()) {
-            @Override public <Row> Iterable<Row> scan(
-                ExecutionContext<Row> execCtx,
-                ColocationGroup grp,
-                ImmutableBitSet requiredColumns
-            ) {
-                return Arrays.asList(
-                    row(execCtx, requiredColumns, 0, "Calcite", 1),
-                    row(execCtx, requiredColumns, 1, "Ignite", 1)
-                );
-            }
-
-            @Override public ColocationGroup 
colocationGroup(MappingQueryContext ctx) {
-                return ColocationGroup.forNodes(select(nodes, 1));
-            }
-
-            @Override public IgniteDistribution distribution() {
-                return IgniteDistributions.broadcast();
-            }
-        };
-
-        IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
-
-        publicSchema.addTable("DEVELOPER", developer);
-        publicSchema.addTable("PROJECT", project);
-
-        String sql = "SELECT d.id, d.name, d.projectId, p.name0, p.ver0 " +
-            "FROM PUBLIC.Developer d JOIN (" +
-            "SELECT pp.id as id0, pp.name as name0, pp.ver as ver0 FROM 
PUBLIC.Project pp" +
-            ") p " +
-            "ON d.projectId = p.id0 " +
-            "WHERE (d.projectId + 1) > ?";
-
-        List<Object[]> res = executeQuery(publicSchema, sql, -10);
-
-        assertFalse(res.isEmpty());
-
-        Assert.assertArrayEquals(new Object[]{0, "Igor", 0, "Calcite", 1}, 
res.get(0));
-        Assert.assertArrayEquals(new Object[]{1, "Roman", 0, "Calcite", 1}, 
res.get(1));
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    @Test
-    public void testPhysicalPlan2() throws Exception {
-        executors = new ArrayList<>();
-
-        IgniteTypeFactory f = new IgniteTypeFactory(IgniteTypeSystem.INSTANCE);
-
-        ThreadLocal<List<?>> checkRes = new ThreadLocal<>();
-
-        TestTable testTbl = new TestTable(
-            new RelDataTypeFactory.Builder(f)
-                .add("ID0", f.createJavaType(Integer.class))
-                .add("ID1", f.createJavaType(Integer.class))
-                .build()) {
-            @Override public <Row> Iterable<Row> scan(
-                ExecutionContext<Row> execCtx,
-                ColocationGroup grp,
-                ImmutableBitSet requiredColumns
-            ) {
-                List<Row> res = new ArrayList<>();
-                List<Row> checkRes0 = new ArrayList<>();
-
-                for (int i = 0; i < 10; ++i) {
-                    int col = ThreadLocalRandom.current().nextInt(1_000);
-
-                    res.add(row(execCtx, requiredColumns, col, col));
-                    checkRes0.add(row(execCtx, null, col + col));
-                }
-
-                checkRes.set(checkRes0);
-
-                return res;
-            }
-
-            @Override public ColocationGroup 
colocationGroup(MappingQueryContext ctx) {
-                return ColocationGroup.forNodes(select(nodes, 1));
-            }
-
-            @Override public IgniteDistribution distribution() {
-                return IgniteDistributions.broadcast();
-            }
-        };
-
-        IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
-
-        publicSchema.addTable("TEST_TABLE", testTbl);
-
-        String sql = "SELECT (ID0 + ID1) AS RES FROM PUBLIC.TEST_TABLE";
-
-        List<Object[]> res = executeQuery(publicSchema, sql);
-
-        assertFalse(res.isEmpty());
-
-        int pos = 0;
-
-        for (Object obj : checkRes.get())
-            Assert.assertArrayEquals((Object[])obj, res.get(pos++));
-    }
-
-    /** */
-    private List<Object[]> executeQuery(IgniteSchema publicSchema, String sql, 
Object... parameters) throws Exception {
-        SchemaPlus schema = createRootSchema(false)
-            .add("PUBLIC", publicSchema);
-
-        BaseQueryContext qctx = BaseQueryContext.builder()
-            .logger(log)
-            .defaultSchema(schema)
-            .build();
-
-        PlanningContext ctx = PlanningContext.builder()
-            .parentContext(qctx)
-            .query(sql)
-            .parameters(parameters)
-            .build();
-
-        IgniteRel phys = physicalPlan(ctx);
-
-        ExecutionPlan plan = splitPlan(phys);
-
-        List<Fragment> fragments = plan.fragments();
-        assertEquals(2, fragments.size());
-
-        UUID qryId = UUID.randomUUID();
-
-        TestIoManager mgr = new TestIoManager();
-        Node<Object[]> exec;
-
-        //// Local part
-
-        Fragment fragment = fragments.get(0);
-        assert fragment.rootFragment();
-
-        exec = implementFragment(qctx, ctx, mgr, plan, fragment, qryId, 
F.first(nodes));
-
-        RootNode<Object[]> consumer = new RootNode<>(exec.context(), 
exec.rowType());
-        consumer.register(exec);
-
-        //// Remote part
-
-        fragment = fragments.get(1);
-
-        assert !fragment.rootFragment();
-
-        exec = implementFragment(qctx, ctx, mgr, plan, fragment, qryId, 
nodes.get(1));
-
-        //// Start execution
-
-        assert exec instanceof Outbox;
-
-        Outbox<Object[]> outbox = (Outbox<Object[]>)exec;
-
-        exec.context().execute(outbox::init, outbox::onError);
-
-        List<Object[]> res = new ArrayList<>();
-
-        while (consumer.hasNext())
-            res.add(consumer.next());
-
-        return res;
-    }
-
-    /** */
-    private ExecutionPlan splitPlan(IgniteRel phys) {
-        assertNotNull(phys);
-
-        MultiStepPlan plan = new MultiStepQueryPlan(null, null, new 
QueryTemplate(new Splitter().go(phys)), null, null);
-
-        assertNotNull(plan);
-
-        return plan.init(this::intermediateMapping, null, 
Commons.mapContext(F.first(nodes), AffinityTopologyVersion.NONE));
-    }
-
-    /**
-     * Transforms fragment to the execution node.
-     */
-    private Node<Object[]> implementFragment(
-        BaseQueryContext qctx,
-        PlanningContext ctx,
-        TestIoManager mgr,
-        ExecutionPlan plan,
-        Fragment fragment,
-        UUID qryId,
-        UUID nodeId
-    ) throws IgniteCheckedException {
-        GridTestKernalContext kernal = newContext();
-        kernal.add(new NoOpIgniteSecurityProcessor(kernal));
-        kernal.add(new GridCacheProcessor(kernal));
-
-        StripedQueryTaskExecutor taskExecutor = new 
StripedQueryTaskExecutor(kernal);
-        taskExecutor.stripedThreadPoolExecutor(new 
IgniteStripedThreadPoolExecutor(
-            kernal.config().getQueryThreadPoolSize(),
-            kernal.igniteInstanceName(),
-            "calciteQry",
-            (t, ex) -> {
-                log().error(ex.getMessage(), ex);
-                lastE = ex;
-            },
-            true,
-            DFLT_THREAD_KEEP_ALIVE_TIME
-        ));
-        executors.add(taskExecutor);
-
-        MessageServiceImpl msgSvc = new TestMessageServiceImpl(kernal, mgr);
-
-        msgSvc.localNodeId(nodeId);
-        msgSvc.taskExecutor(taskExecutor);
-        mgr.register(msgSvc);
-
-        MailboxRegistryImpl mailboxRegistry = new MailboxRegistryImpl(kernal);
-
-        ExchangeServiceImpl exchangeSvc = new ExchangeServiceImpl(kernal);
-        exchangeSvc.taskExecutor(taskExecutor);
-        exchangeSvc.messageService(msgSvc);
-        exchangeSvc.mailboxRegistry(mailboxRegistry);
-        exchangeSvc.queryRegistry(new QueryRegistryImpl(kernal));
-        exchangeSvc.init();
-
-        ExecutionContext<Object[]> ectx = new ExecutionContext<>(
-            qctx,
-            taskExecutor,
-            null,
-            qryId,
-            nodeId,
-            F.first(nodes),
-            AffinityTopologyVersion.NONE,
-            new FragmentDescription(
-                fragment.fragmentId(),
-                fragment.mapping(),
-                plan.target(fragment),
-                plan.remotes(fragment)),
-            ArrayRowHandler.INSTANCE,
-            NoOpMemoryTracker.INSTANCE,
-            NoOpIoTracker.INSTANCE,
-            0,
-            Commons.parametersMap(ctx.parameters()),
-            null);
-
-        return new LogicalRelImplementor<>(ectx, c -> r -> 0, mailboxRegistry, 
exchangeSvc,
-            new TestFailureProcessor(kernal)).go(fragment.root());
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    @Test
-    public void testSplitterColocatedReplicatedReplicated() throws Exception {
-        IgniteTypeFactory f = new IgniteTypeFactory(IgniteTypeSystem.INSTANCE);
-
-        TestTable developer = new TestTable(
-            new RelDataTypeFactory.Builder(f)
-                .add("ID", f.createJavaType(Integer.class))
-                .add("NAME", f.createJavaType(String.class))
-                .add("PROJECTID", f.createJavaType(Integer.class))
-                .build()) {
-            @Override public ColocationGroup 
colocationGroup(MappingQueryContext ctx) {
-                return ColocationGroup.forNodes(select(nodes, 0, 1, 2, 3));
-            }
-
-            @Override public IgniteDistribution distribution() {
-                return IgniteDistributions.broadcast();
-            }
-        };
-
-        TestTable project = new TestTable(
-            new RelDataTypeFactory.Builder(f)
-                .add("ID", f.createJavaType(Integer.class))
-                .add("NAME", f.createJavaType(String.class))
-                .add("VER", f.createJavaType(Integer.class))
-                .build()) {
-            @Override public ColocationGroup 
colocationGroup(MappingQueryContext ctx) {
-                return ColocationGroup.forNodes(select(nodes, 0, 1, 2, 3));
-            }
-
-            @Override public IgniteDistribution distribution() {
-                return IgniteDistributions.broadcast();
-            }
-        };
-
-        IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
-
-        publicSchema.addTable("DEVELOPER", developer);
-        publicSchema.addTable("PROJECT", project);
-
-        String sql = "SELECT d.id, (d.id + 1) as id2, d.name, d.projectId, 
p.id0, p.ver0 " +
-            "FROM PUBLIC.Developer d JOIN (" +
-            "SELECT pp.id as id0, pp.ver as ver0 FROM PUBLIC.Project pp" +
-            ") p " +
-            "ON d.id = p.id0 " +
-            "WHERE (d.projectId + 1) > ?";
-
-        IgniteRel phys = physicalPlan(sql, publicSchema);
-
-        assertEquals(1, splitPlan(phys).fragments().size());
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    @Test
-    public void testSplitterPartiallyColocatedReplicatedAndPartitioned() 
throws Exception {
-        IgniteTypeFactory f = new IgniteTypeFactory(IgniteTypeSystem.INSTANCE);
-
-        TestTable developer = new TestTable(
-            new RelDataTypeFactory.Builder(f)
-                .add("ID", f.createJavaType(Integer.class))
-                .add("NAME", f.createJavaType(String.class))
-                .add("PROJECTID", f.createJavaType(Integer.class))
-                .build()) {
-            @Override public ColocationGroup 
colocationGroup(MappingQueryContext ctx) {
-                return ColocationGroup.forNodes(select(nodes, 0));
-            }
-
-            @Override public IgniteDistribution distribution() {
-                return IgniteDistributions.broadcast();
-            }
-        };
-
-        TestTable project = new TestTable(
-            new RelDataTypeFactory.Builder(f)
-                .add("ID", f.createJavaType(Integer.class))
-                .add("NAME", f.createJavaType(String.class))
-                .add("VER", f.createJavaType(Integer.class))
-                .build()) {
-            @Override public ColocationGroup 
colocationGroup(MappingQueryContext ctx) {
-                return ColocationGroup.forAssignments(Arrays.asList(
-                    select(nodes, 1, 2),
-                    select(nodes, 2, 3),
-                    select(nodes, 3, 0),
-                    select(nodes, 0, 1)
-                ));
-            }
-
-            @Override public IgniteDistribution distribution() {
-                return IgniteDistributions.affinity(0, "Project", "hash");
-            }
-        };
-
-        IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
-
-        publicSchema.addTable("DEVELOPER", developer);
-        publicSchema.addTable("PROJECT", project);
-
-        String sql = "SELECT d.id, d.name, d.projectId, p.id0, p.ver0 " +
-            "FROM PUBLIC.Developer d JOIN (" +
-            "SELECT pp.id as id0, pp.ver as ver0 FROM PUBLIC.Project pp" +
-            ") p " +
-            "ON d.id = p.id0 " +
-            "WHERE (d.projectId + 1) > ?";
-
-        IgniteRel phys = physicalPlan(sql, publicSchema);
-
-        assertEquals(3, splitPlan(phys).fragments().size());
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    @Test
-    public void testSplitterPartiallyColocated1() throws Exception {
-        IgniteTypeFactory f = new IgniteTypeFactory(IgniteTypeSystem.INSTANCE);
-
-        TestTable developer = new TestTable(
-            new RelDataTypeFactory.Builder(f)
-                .add("ID", f.createJavaType(Integer.class))
-                .add("NAME", f.createJavaType(String.class))
-                .add("PROJECTID", f.createJavaType(Integer.class))
-                .build()) {
-            @Override public ColocationGroup 
colocationGroup(MappingQueryContext ctx) {
-                return ColocationGroup.forNodes(select(nodes, 1, 2, 3));
-            }
-
-            @Override public IgniteDistribution distribution() {
-                return IgniteDistributions.broadcast();
-            }
-        };
-
-        TestTable project = new TestTable(
-            new RelDataTypeFactory.Builder(f)
-                .add("ID", f.createJavaType(Integer.class))
-                .add("NAME", f.createJavaType(String.class))
-                .add("VER", f.createJavaType(Integer.class))
-                .build()) {
-            @Override public ColocationGroup 
colocationGroup(MappingQueryContext ctx) {
-                return ColocationGroup.forAssignments(Arrays.asList(
-                    select(nodes, 0),
-                    select(nodes, 1),
-                    select(nodes, 2)
-                ));
-            }
-
-            @Override public IgniteDistribution distribution() {
-                return IgniteDistributions.affinity(0, "Project", "hash");
-            }
-        };
-
-        IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
-
-        publicSchema.addTable("DEVELOPER", developer);
-        publicSchema.addTable("PROJECT", project);
-
-        String sql = "SELECT d.id, d.name, d.projectId, p.id0, p.ver0 " +
-            "FROM PUBLIC.Developer d JOIN (" +
-            "SELECT pp.id as id0, pp.ver as ver0 FROM PUBLIC.Project pp" +
-            ") p " +
-            "ON d.projectId = p.id0 " +
-            "WHERE (d.projectId + 1) > ?";
-
-        IgniteRel phys = physicalPlan(sql, publicSchema);
-
-        assertEquals(3, splitPlan(phys).fragments().size());
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    @Test
-    public void testSplitterPartiallyColocated2() throws Exception {
-        IgniteTypeFactory f = new IgniteTypeFactory(IgniteTypeSystem.INSTANCE);
-
-        TestTable developer = new TestTable(
-            new RelDataTypeFactory.Builder(f)
-                .add("ID", f.createJavaType(Integer.class))
-                .add("NAME", f.createJavaType(String.class))
-                .add("PROJECTID", f.createJavaType(Integer.class))
-                .build()) {
-            @Override public ColocationGroup 
colocationGroup(MappingQueryContext ctx) {
-                return ColocationGroup.forNodes(select(nodes, 0));
-            }
-
-            @Override public IgniteDistribution distribution() {
-                return IgniteDistributions.broadcast();
-            }
-        };
-
-        TestTable project = new TestTable(
-            new RelDataTypeFactory.Builder(f)
-                .add("ID", f.createJavaType(Integer.class))
-                .add("NAME", f.createJavaType(String.class))
-                .add("VER", f.createJavaType(Integer.class))
-                .build()) {
-            @Override public ColocationGroup 
colocationGroup(MappingQueryContext ctx) {
-                return ColocationGroup.forAssignments(Arrays.asList(
-                    select(nodes, 1),
-                    select(nodes, 2),
-                    select(nodes, 3)
-                ));
-            }
-
-            @Override public IgniteDistribution distribution() {
-                return IgniteDistributions.affinity(0, "Project", "hash");
-            }
-        };
-
-        IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
-
-        publicSchema.addTable("DEVELOPER", developer);
-        publicSchema.addTable("PROJECT", project);
-
-        String sql = "SELECT d.id, d.name, d.projectId, p.id0, p.ver0 " +
-            "FROM PUBLIC.Developer d JOIN (" +
-            "SELECT pp.id as id0, pp.ver as ver0 FROM PUBLIC.Project pp" +
-            ") p " +
-            "ON d.projectId = p.id0 " +
-            "WHERE (d.projectId + 1) > ?";
-
-        IgniteRel phys = physicalPlan(sql, publicSchema);
-
-        assertEquals(3, splitPlan(phys).fragments().size());
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    @Test
-    public void testSplitterNonColocated() throws Exception {
-        IgniteTypeFactory f = new IgniteTypeFactory(IgniteTypeSystem.INSTANCE);
-
-        TestTable developer = new TestTable(
-            new RelDataTypeFactory.Builder(f)
-                .add("ID", f.createJavaType(Integer.class))
-                .add("NAME", f.createJavaType(String.class))
-                .add("PROJECTID", f.createJavaType(Integer.class))
-                .build()) {
-            @Override public ColocationGroup 
colocationGroup(MappingQueryContext ctx) {
-                return ColocationGroup.forNodes(select(nodes, 2));
-            }
-
-            @Override public IgniteDistribution distribution() {
-                return IgniteDistributions.broadcast();
-            }
-        };
-
-        TestTable project = new TestTable(
-            new RelDataTypeFactory.Builder(f)
-                .add("ID", f.createJavaType(Integer.class))
-                .add("NAME", f.createJavaType(String.class))
-                .add("VER", f.createJavaType(Integer.class))
-                .build()) {
-            @Override public ColocationGroup 
colocationGroup(MappingQueryContext ctx) {
-                return ColocationGroup.forNodes(select(nodes, 0, 1));
-            }
-
-            @Override public IgniteDistribution distribution() {
-                return IgniteDistributions.broadcast();
-            }
-        };
-
-        IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
-
-        publicSchema.addTable("DEVELOPER", developer);
-        publicSchema.addTable("PROJECT", project);
-
-        String sql = "SELECT p.id0, d.id " +
-            "FROM PUBLIC.Developer d JOIN (" +
-            "SELECT pp.id as id0, pp.ver as ver0 FROM PUBLIC.Project pp" +
-            ") p " +
-            "ON d.projectId = p.ver0 " +
-            "WHERE (d.projectId + 1) > ?";
-
-        IgniteRel phys = physicalPlan(sql, publicSchema);
-
-        assertEquals(2, splitPlan(phys).fragments().size());
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    @Test
-    public void testMergeFilters() throws Exception {
-        IgniteTypeFactory f = new IgniteTypeFactory(IgniteTypeSystem.INSTANCE);
-
-        TestTable testTbl = new TestTable(
-            new RelDataTypeFactory.Builder(f)
-                .add("ID", f.createJavaType(Integer.class))
-                .add("VAL", f.createJavaType(String.class))
-                .build()) {
-            @Override public IgniteDistribution distribution() {
-                return IgniteDistributions.single();
-            }
-        };
-
-        IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
-
-        publicSchema.addTable("TEST", testTbl);
-
-        String sql = "" +
-            "SELECT val from (\n" +
-            "   SELECT * \n" +
-            "   FROM TEST \n" +
-            "   WHERE VAL = 10) \n" +
-            "WHERE VAL = 10";
-
-        RelNode phys = physicalPlan(sql, publicSchema);
-
-        assertNotNull(phys);
-
-        AtomicInteger filterCnt = new AtomicInteger();
-
-        // Counts filters af the plan.
-        phys.childrenAccept(
-            new RelVisitor() {
-                @Override public void visit(RelNode node, int ordinal, RelNode 
parent) {
-                    if (node instanceof IgniteFilter)
-                        filterCnt.incrementAndGet();
-
-                    super.visit(node, ordinal, parent);
-                }
-            }
-        );
-
-        // Checks that two filter merged into one filter.
-        // Expected plan:
-        // IgniteProject(VAL=[$1])
-        //  IgniteProject(ID=[$0], VAL=[$1])
-        //    IgniteFilter(condition=[=(CAST($1):INTEGER, 10)])
-        //      IgniteTableScan(table=[[PUBLIC, TEST]])
-        assertEquals(0, filterCnt.get());
-    }
-
-    /**
-     * @throws Exception If failed.
-     */
-    @Test
-    public void testJoinPushExpressionRule() throws Exception {
-        IgniteSchema publicSchema = createSchema(
-            createTable("EMP", 100, IgniteDistributions.broadcast(),
-                "ID", INTEGER, "NAME", VARCHAR, "DEPTNO", INTEGER),
-            createTable("DEPT", 10, IgniteDistributions.broadcast(),
-                "DEPTNO", INTEGER, "NAME", VARCHAR)
-        );
-
-        String sql = "select /*+ CNL_JOIN */ d.deptno, e.deptno " +
-            "from dept d, emp e " +
-            "where d.deptno + e.deptno = 2";
-
-        assertPlan(sql, publicSchema, 
isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)
-            .and(cnlj -> "=(+($0, $1), 
2)".equals(cnlj.getCondition().toString()))
-            .and(cnlj -> cnlj.getJoinType() == JoinRelType.INNER)
-            .and(cnlj -> cnlj.getVariablesSet().size() == 1)
-            .and(input(0, isTableScan("DEPT")
-                .and(t -> t.requiredColumns().equals(ImmutableBitSet.of(0)))
-            ))
-            .and(input(1, isTableScan("EMP")
-                .and(t -> t.requiredColumns().equals(ImmutableBitSet.of(2)))
-                .and(t -> "=(+($cor1.DEPTNO, $t0), 
2)".equals(t.condition().toString()))
-            ))
-        );
-    }
-
-    /** */
-    @Test
-    public void testMergeJoinIsNotAppliedForNonEquiJoin() throws Exception {
-        IgniteTypeFactory f = new IgniteTypeFactory(IgniteTypeSystem.INSTANCE);
-
-        TestTable emp = new TestTable(
-            new RelDataTypeFactory.Builder(f)
-                .add("ID", f.createJavaType(Integer.class))
-                .add("NAME", f.createJavaType(String.class))
-                .add("DEPTNO", f.createJavaType(Integer.class))
-                .build(), 1000) {
-
-            @Override public IgniteDistribution distribution() {
-                return IgniteDistributions.broadcast();
-            }
-        };
-
-        emp.addIndex("emp_idx", 1, 2);
-
-        TestTable dept = new TestTable(
-            new RelDataTypeFactory.Builder(f)
-                .add("DEPTNO", f.createJavaType(Integer.class))
-                .add("NAME", f.createJavaType(String.class))
-                .build(), 100) {
-
-            @Override public IgniteDistribution distribution() {
-                return IgniteDistributions.broadcast();
-            }
-        };
-
-        dept.addIndex("dep_idx", 1, 0);
-
-        IgniteSchema publicSchema = new IgniteSchema("PUBLIC");
-
-        publicSchema.addTable("EMP", emp);
-        publicSchema.addTable("DEPT", dept);
-
-        String sql = "select d.deptno, d.name, e.id, e.name from dept d join 
emp e " +
-            "on d.deptno = e.deptno and e.name >= d.name order by e.name, 
d.deptno";
-
-        RelNode phys = physicalPlan(sql, publicSchema, 
"CorrelatedNestedLoopJoin");
-
-        assertNotNull(phys);
-        assertEquals("" +
-                "IgniteSort(sort0=[$3], sort1=[$0], dir0=[ASC-nulls-first], 
dir1=[ASC-nulls-first])\n" +
-                "  IgniteProject(DEPTNO=[$3], NAME=[$4], ID=[$0], 
NAME0=[$1])\n" +
-                "    IgniteNestedLoopJoin(condition=[AND(=($3, $2), >=($1, 
$4))], joinType=[inner])\n" +
-                "      IgniteTableScan(table=[[PUBLIC, EMP]])\n" +
-                "      IgniteTableScan(table=[[PUBLIC, DEPT]])\n",
-            RelOptUtil.toString(phys));
-    }
-}
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/ProjectFilterScanMergePlannerTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/ProjectFilterScanMergePlannerTest.java
index 7eb17b7eaef..0c64365efb8 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/ProjectFilterScanMergePlannerTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/ProjectFilterScanMergePlannerTest.java
@@ -246,6 +246,18 @@ public class ProjectFilterScanMergePlannerTest extends 
AbstractPlannerTest {
             "ProjectFilterTransposeRule", "FilterProjectTransposeRule");
     }
 
+    /** */
+    @Test
+    public void testFilterFilterMerge() throws Exception {
+        String sql = "SELECT * FROM (SELECT a FROM tbl WHERE a = 1) WHERE a = 
1";
+
+        assertPlan(sql, publicSchema, isInstanceOf(IgniteTableScan.class)
+            .and(scan -> scan.projects() == null)
+            .and(scan -> scan.condition() != null)
+            .and(scan -> "=($t0, 1)".equals(scan.condition().toString()))
+            .and(scan -> 
ImmutableBitSet.of(0).equals(scan.requiredColumns())));
+    }
+
     /** */
     private static List<RexNode> searchBoundsCondition(List<SearchBounds> 
searchBounds) {
         return 
searchBounds.stream().filter(Objects::nonNull).map(SearchBounds::condition).collect(Collectors.toList());
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/TestTable.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/TestTable.java
index c0220b08028..43ab660fbc9 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/TestTable.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/TestTable.java
@@ -83,6 +83,9 @@ public class TestTable implements IgniteCacheTable {
     /** */
     private IgniteDistribution distribution;
 
+    /** */
+    private ColocationGroup colocationGrp;
+
     /** */
     private IgniteStatisticsImpl statistics;
 
@@ -123,6 +126,18 @@ public class TestTable implements IgniteCacheTable {
         return this;
     }
 
+    /**
+     * Set colocation group.
+     *
+     * @param colocationGrp Colocation group to set.
+     * @return TestTable for chaining.
+     */
+    public TestTable setColocationGroup(ColocationGroup colocationGrp) {
+        this.colocationGrp = colocationGrp;
+
+        return this;
+    }
+
     /**
      * Set table statistics;
      *
@@ -197,6 +212,9 @@ public class TestTable implements IgniteCacheTable {
 
     /** {@inheritDoc} */
     @Override public ColocationGroup colocationGroup(MappingQueryContext ctx) {
+        if (colocationGrp != null)
+            return colocationGrp;
+
         throw new AssertionError();
     }
 
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java
 
b/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java
index 86361212b9d..eec724a9a64 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/testsuites/PlannerTestSuite.java
@@ -32,7 +32,8 @@ import 
org.apache.ignite.internal.processors.query.calcite.planner.JoinCommutePl
 import 
org.apache.ignite.internal.processors.query.calcite.planner.JoinWithUsingPlannerTest;
 import 
org.apache.ignite.internal.processors.query.calcite.planner.LimitOffsetPlannerTest;
 import 
org.apache.ignite.internal.processors.query.calcite.planner.MergeJoinPlannerTest;
-import org.apache.ignite.internal.processors.query.calcite.planner.PlannerTest;
+import 
org.apache.ignite.internal.processors.query.calcite.planner.PlanExecutionTest;
+import 
org.apache.ignite.internal.processors.query.calcite.planner.PlanSplitterTest;
 import 
org.apache.ignite.internal.processors.query.calcite.planner.PlannerTimeoutTest;
 import 
org.apache.ignite.internal.processors.query.calcite.planner.ProjectFilterScanMergePlannerTest;
 import 
org.apache.ignite.internal.processors.query.calcite.planner.RexSimplificationPlannerTest;
@@ -56,7 +57,8 @@ import org.junit.runners.Suite;
  */
 @RunWith(Suite.class)
 @Suite.SuiteClasses({
-    PlannerTest.class,
+    PlanExecutionTest.class,
+    PlanSplitterTest.class,
     CorrelatedNestedLoopJoinPlannerTest.class,
     TableSpoolPlannerTest.class,
     SortedIndexSpoolPlannerTest.class,

Reply via email to