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

gvvinblade pushed a commit to branch ignite-12248
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/ignite-12248 by this push:
     new 4fdf5dc  IGNITE-13476: Calcite improvements. Implement ProjectionNode. 
This closes #8302
4fdf5dc is described below

commit 4fdf5dcc1972b2525898312b88e0aa72df28aaef
Author: zstan <[email protected]>
AuthorDate: Fri Oct 2 16:16:57 2020 +0300

    IGNITE-13476: Calcite improvements. Implement ProjectionNode. This closes 
#8302
---
 modules/calcite/pom.xml                            |   7 +-
 .../processors/query/calcite/exec/IndexScan.java   |  28 +++-
 .../query/calcite/exec/LogicalRelImplementor.java  |  31 ++++-
 .../processors/query/calcite/exec/TableScan.java   |  37 +++--
 .../query/calcite/metadata/IgniteMdPredicates.java |   4 +-
 .../processors/query/calcite/prepare/Cloner.java   |   6 +-
 .../query/calcite/prepare/PlannerPhase.java        |  15 +-
 .../query/calcite/rel/FilterableTableScan.java     |  72 ----------
 .../query/calcite/rel/IgniteIndexScan.java         |  52 +++++--
 .../query/calcite/rel/IgniteProject.java           |  48 +++----
 .../query/calcite/rel/IgniteTableScan.java         |  28 +++-
 .../rel/ProjectableFilterableTableScan.java        | 136 ++++++++++++++++++
 .../query/calcite/rule/ExposeIndexRule.java        |   6 +-
 ...rIntoScanRule.java => FilterScanMergeRule.java} |  66 +++++++--
 .../query/calcite/rule/ProjectScanMergeRule.java   | 153 +++++++++++++++++++++
 .../rule/logical/LogicalProjectMergeRule.java      |  47 +++++++
 .../rule/logical/LogicalProjectRemoveRule.java     |  49 +++++++
 .../query/calcite/schema/IgniteIndex.java          |  11 +-
 .../query/calcite/schema/IgniteTable.java          |  26 +++-
 .../query/calcite/schema/IgniteTableImpl.java      |  25 ++--
 .../query/calcite/schema/TableDescriptor.java      |  25 +++-
 .../query/calcite/schema/TableDescriptorImpl.java  |  64 +++++----
 .../processors/query/calcite/trait/TraitUtils.java |  25 +++-
 .../processors/query/calcite/util/RexUtils.java    |  37 ++++-
 .../query/calcite/CalciteQueryProcessorTest.java   |   3 +-
 .../processors/query/calcite/PlannerTest.java      | 132 +++++++++++++-----
 .../processors/query/calcite/QueryChecker.java     | 105 +++++++++++++-
 .../processors/query/calcite/QueryCheckerTest.java |  43 ++++++
 .../query/calcite/rules/OrToUnionRuleTest.java     |   2 +-
 .../calcite/rules/ProjectScanMergeRuleTest.java    | 116 ++++++++++++++++
 .../ignite/testsuites/IgniteCalciteTestSuite.java  |   7 +-
 31 files changed, 1159 insertions(+), 247 deletions(-)

diff --git a/modules/calcite/pom.xml b/modules/calcite/pom.xml
index 47ce428..c3452e5 100644
--- a/modules/calcite/pom.xml
+++ b/modules/calcite/pom.xml
@@ -74,12 +74,6 @@
         </dependency>
 
         <dependency>
-            <groupId>org.apache.calcite</groupId>
-            <artifactId>calcite-linq4j</artifactId>
-            <version>${calcite.version}</version>
-        </dependency>
-
-        <dependency>
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
             <version>${guava.version}</version>
@@ -174,6 +168,7 @@
             <version>${spring.version}</version>
             <scope>test</scope>
         </dependency>
+
         <dependency>
             <groupId>org.apache.ignite</groupId>
             <artifactId>ignite-clients</artifactId>
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/IndexScan.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/IndexScan.java
index f6f595a..dcc178e 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/IndexScan.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/IndexScan.java
@@ -21,9 +21,11 @@ import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.NoSuchElementException;
+import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteException;
 import org.apache.ignite.cluster.ClusterTopologyException;
@@ -54,6 +56,7 @@ import org.apache.ignite.spi.indexing.IndexingQueryFilterImpl;
 import org.h2.value.DataType;
 import org.h2.value.Value;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Scan on index.
@@ -101,6 +104,12 @@ public class IndexScan<Row> implements Iterable<Row>, 
AutoCloseable {
     /** */
     private volatile List<GridDhtLocalPartition> reserved;
 
+    /** */
+    private final Function<Row, Row> rowTransformer;
+
+    /** */
+    private final ImmutableBitSet requiredColunms;
+
     /**
      * @param ectx Execution context.
      * @param desc Table descriptor.
@@ -115,7 +124,9 @@ public class IndexScan<Row> implements Iterable<Row>, 
AutoCloseable {
         GridIndex<H2Row> idx,
         Predicate<Row> filters,
         Supplier<Row> lowerBound,
-        Supplier<Row> upperBound
+        Supplier<Row> upperBound,
+        Function<Row, Row> rowTransformer,
+        @Nullable ImmutableBitSet requiredColunms
     ) {
         this.ectx = ectx;
         this.desc = desc;
@@ -123,7 +134,7 @@ public class IndexScan<Row> implements Iterable<Row>, 
AutoCloseable {
         kctx = cctx.kernalContext();
         coCtx = cctx.cacheObjectContext();
 
-        RelDataType rowType = desc.selectRowType(this.ectx.getTypeFactory());
+        RelDataType rowType = desc.rowType(this.ectx.getTypeFactory(), 
requiredColunms);
 
         factory = this.ectx.rowHandler().factory(this.ectx.getTypeFactory(), 
rowType);
         this.idx = idx;
@@ -133,6 +144,8 @@ public class IndexScan<Row> implements Iterable<Row>, 
AutoCloseable {
         this.upperBound = upperBound;
         partsArr = ectx.localPartitions();
         mvccSnapshot = ectx.mvccSnapshot();
+        this.rowTransformer = rowTransformer;
+        this.requiredColunms = requiredColunms;
     }
 
     /** {@inheritDoc} */
@@ -314,10 +327,15 @@ public class IndexScan<Row> implements Iterable<Row>, 
AutoCloseable {
             while (next == null && cursor.next()) {
                 H2Row h2Row = cursor.get();
 
-                Row r = desc.toRow(ectx, (CacheDataRow)h2Row, factory);
+                Row r = desc.toRow(ectx, (CacheDataRow)h2Row, factory, 
requiredColunms);
+
+                if (filters != null && !filters.test(r))
+                    continue;
+
+                if (rowTransformer != null)
+                    r = rowTransformer.apply(r);
 
-                if (filters == null || filters.test(r))
-                    next = r;
+                next = r;
             }
         }
     }
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java
index 5c01ed7..11bfd8a 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java
@@ -73,6 +73,7 @@ import 
org.apache.ignite.internal.processors.query.calcite.schema.TableDescripto
 import org.apache.ignite.internal.processors.query.calcite.trait.Destination;
 import 
org.apache.ignite.internal.processors.query.calcite.trait.DistributionFunction;
 import 
org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
+import 
org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
 import org.apache.ignite.internal.processors.query.calcite.util.Commons;
 import org.apache.ignite.internal.util.typedef.F;
 
@@ -216,16 +217,24 @@ public class LogicalRelImplementor<Row> implements 
IgniteRelVisitor<Node<Row>> {
     /** {@inheritDoc} */
     @Override public Node<Row> visit(IgniteIndexScan rel) {
         RexNode condition = rel.condition();
-        Predicate<Row> filters = condition == null ? null : 
expressionFactory.predicate(condition, rel.getRowType());
+        List<RexNode> projects = rel.projects();
 
-        List<RexNode> lowerCond = rel.lowerIndexCondition();
-        Supplier<Row> lower = lowerCond == null ? null : 
expressionFactory.rowSource(lowerCond);
+        IgniteTable tbl = rel.getTable().unwrap(IgniteTable.class);
+        IgniteTypeFactory typeFactory = ctx.getTypeFactory();
 
+        ImmutableBitSet requiredColunms = rel.requiredColunms();
+        List<RexNode> lowerCond = rel.lowerIndexCondition();
         List<RexNode> upperCond = rel.upperIndexCondition();
+
+        RelDataType cols = tbl.getRowType(typeFactory, requiredColunms);
+
+        Predicate<Row> filters = condition == null ? null : 
expressionFactory.predicate(condition, cols);
+        Supplier<Row> lower = lowerCond == null ? null : 
expressionFactory.rowSource(lowerCond);
         Supplier<Row> upper = upperCond == null ? null : 
expressionFactory.rowSource(upperCond);
+        Function<Row, Row> prj = projects == null ? null : 
expressionFactory.project(projects, cols);
 
-        IgniteIndex idx = 
rel.getTable().unwrap(IgniteTable.class).getIndex(rel.indexName());
-        Iterable<Row> rowsIter = idx.scan(ctx, filters, lower, upper);
+        IgniteIndex idx = tbl.getIndex(rel.indexName());
+        Iterable<Row> rowsIter = idx.scan(ctx, filters, lower, upper, prj, 
requiredColunms);
 
         return new ScanNode<>(ctx, rowsIter);
     }
@@ -233,10 +242,18 @@ public class LogicalRelImplementor<Row> implements 
IgniteRelVisitor<Node<Row>> {
     /** {@inheritDoc} */
     @Override public Node<Row> visit(IgniteTableScan rel) {
         RexNode condition = rel.condition();
-        Predicate<Row> filters = condition == null ? null : 
expressionFactory.predicate(condition, rel.getRowType());
+        List<RexNode> projects = rel.projects();
+        ImmutableBitSet requiredColunms = rel.requiredColunms();
 
         IgniteTable tbl = rel.getTable().unwrap(IgniteTable.class);
-        Iterable<Row> rowsIter = tbl.scan(ctx, filters);
+        IgniteTypeFactory typeFactory = ctx.getTypeFactory();
+
+        RelDataType cols = tbl.getRowType(typeFactory, requiredColunms);
+
+        Predicate<Row> filters = condition == null ? null : 
expressionFactory.predicate(condition, cols);
+        Function<Row, Row> prj = projects == null ? null : 
expressionFactory.project(projects, cols);
+
+        Iterable<Row> rowsIter = tbl.scan(ctx, filters, prj, requiredColunms);
 
         return new ScanNode<>(ctx, rowsIter);
     }
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableScan.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableScan.java
index c9d126e..b4690ef 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableScan.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableScan.java
@@ -24,8 +24,10 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.Queue;
+import java.util.function.Function;
 import java.util.function.Predicate;
 import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.cluster.ClusterTopologyException;
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
@@ -41,6 +43,8 @@ import 
org.apache.ignite.internal.processors.query.calcite.exec.RowHandler.RowFa
 import 
org.apache.ignite.internal.processors.query.calcite.schema.TableDescriptor;
 import org.apache.ignite.internal.util.lang.GridCursor;
 import org.apache.ignite.internal.util.lang.GridIteratorAdapter;
+import org.apache.ignite.internal.util.typedef.F;
+import org.jetbrains.annotations.Nullable;
 
 /** */
 public class TableScan<Row> implements Iterable<Row>, AutoCloseable {
@@ -72,13 +76,27 @@ public class TableScan<Row> implements Iterable<Row>, 
AutoCloseable {
     private volatile List<GridDhtLocalPartition> reserved;
 
     /** */
-    public TableScan(ExecutionContext<Row> ectx, TableDescriptor desc, 
Predicate<Row> filters) {
+    private final Function<Row, Row> rowTransformer;
+
+    /** Participating colunms. */
+    private final ImmutableBitSet requiredColunms;
+
+    /** */
+    public TableScan(
+        ExecutionContext<Row> ectx,
+        TableDescriptor desc,
+        Predicate<Row> filters,
+        Function<Row, Row> rowTransformer,
+        @Nullable ImmutableBitSet requiredColunms
+    ) {
         this.ectx = ectx;
         cctx = desc.cacheContext();
         this.desc = desc;
         this.filters = filters;
+        this.rowTransformer = rowTransformer;
+        this.requiredColunms = requiredColunms;
 
-        RelDataType rowType = desc.selectRowType(this.ectx.getTypeFactory());
+        RelDataType rowType = desc.rowType(this.ectx.getTypeFactory(), 
requiredColunms);
 
         factory = this.ectx.rowHandler().factory(this.ectx.getTypeFactory(), 
rowType);
         topVer = ectx.planningContext().topologyVersion();
@@ -170,11 +188,10 @@ public class TableScan<Row> implements Iterable<Row>, 
AutoCloseable {
 
     /** */
     private synchronized void release() {
-        if (reserved == null)
+        if (F.isEmpty(reserved))
             return;
 
-        for (GridDhtLocalPartition part : reserved)
-            part.release();
+        reserved.forEach(GridDhtLocalPartition::release);
 
         reserved = null;
     }
@@ -192,6 +209,7 @@ public class TableScan<Row> implements Iterable<Row>, 
AutoCloseable {
         /** */
         private Row next;
 
+        /** */
         private IteratorImpl() {
             assert reserved != null;
 
@@ -246,15 +264,18 @@ public class TableScan<Row> implements Iterable<Row>, 
AutoCloseable {
                     if (!desc.match(row))
                         continue;
 
-                    Row r = desc.toRow(ectx, row, factory);
+                    Row r = desc.toRow(ectx, row, factory, requiredColunms);
+
                     if (filters != null && !filters.test(r))
                         continue;
 
+                    if (rowTransformer != null)
+                        r = rowTransformer.apply(r);
+
                     next = r;
                     break;
-                } else {
+                } else
                     cur = null;
-                }
             }
         }
     }
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdPredicates.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdPredicates.java
index ffd92ca..cc39748 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdPredicates.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/metadata/IgniteMdPredicates.java
@@ -29,7 +29,7 @@ import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexShuttle;
 import org.apache.calcite.rex.RexUtil;
 import org.apache.calcite.util.BuiltInMethod;
-import 
org.apache.ignite.internal.processors.query.calcite.rel.FilterableTableScan;
+import 
org.apache.ignite.internal.processors.query.calcite.rel.ProjectableFilterableTableScan;
 
 /** */
 @SuppressWarnings("unused") // actually all methods are used by runtime 
generated classes
@@ -41,7 +41,7 @@ public class IgniteMdPredicates extends RelMdPredicates {
     /**
      * See {@link 
RelMdPredicates#getPredicates(org.apache.calcite.rel.RelNode, 
org.apache.calcite.rel.metadata.RelMetadataQuery)}
      */
-    public RelOptPredicateList getPredicates(FilterableTableScan rel, 
RelMetadataQuery mq) {
+    public RelOptPredicateList getPredicates(ProjectableFilterableTableScan 
rel, RelMetadataQuery mq) {
         if (rel.condition() == null)
             return RelOptPredicateList.EMPTY;
 
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/Cloner.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/Cloner.java
index aeee7f5..44e6a08 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/Cloner.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/Cloner.java
@@ -138,12 +138,14 @@ class Cloner implements IgniteRelVisitor<IgniteRel> {
 
     /** {@inheritDoc} */
     @Override public IgniteRel visit(IgniteIndexScan rel) {
-        return new IgniteIndexScan(cluster, rel.getTraitSet(), rel.getTable(), 
rel.indexName(), rel.condition());
+        return new IgniteIndexScan(cluster, rel.getTraitSet(), rel.getTable(), 
rel.indexName(), rel.projects(),
+            rel.condition(), rel.requiredColunms());
     }
 
     /** {@inheritDoc} */
     @Override public IgniteRel visit(IgniteTableScan rel) {
-        return new IgniteTableScan(cluster, rel.getTraitSet(), rel.getTable(), 
rel.condition());
+        return new IgniteTableScan(cluster, rel.getTraitSet(), rel.getTable(), 
rel.projects(), rel.condition(),
+            rel.requiredColunms());
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerPhase.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerPhase.java
index ef2e20b..b8d04d2 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerPhase.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerPhase.java
@@ -18,7 +18,6 @@
 package org.apache.ignite.internal.processors.query.calcite.prepare;
 
 import org.apache.calcite.rel.rules.ProjectFilterTransposeRule;
-import org.apache.calcite.rel.rules.ProjectMergeRule;
 import org.apache.calcite.rel.rules.SortRemoveRule;
 import org.apache.calcite.rel.rules.SubQueryRemoveRule;
 import org.apache.calcite.rel.rules.UnionMergeRule;
@@ -29,9 +28,10 @@ import 
org.apache.ignite.internal.processors.query.calcite.rule.AggregateConvert
 import 
org.apache.ignite.internal.processors.query.calcite.rule.CorrelatedNestedLoopJoinConverterRule;
 import 
org.apache.ignite.internal.processors.query.calcite.rule.ExposeIndexRule;
 import 
org.apache.ignite.internal.processors.query.calcite.rule.FilterConverterRule;
+import 
org.apache.ignite.internal.processors.query.calcite.rule.FilterScanMergeRule;
 import 
org.apache.ignite.internal.processors.query.calcite.rule.NestedLoopJoinConverterRule;
 import 
org.apache.ignite.internal.processors.query.calcite.rule.ProjectConverterRule;
-import 
org.apache.ignite.internal.processors.query.calcite.rule.PushFilterIntoScanRule;
+import 
org.apache.ignite.internal.processors.query.calcite.rule.ProjectScanMergeRule;
 import 
org.apache.ignite.internal.processors.query.calcite.rule.SortConverterRule;
 import 
org.apache.ignite.internal.processors.query.calcite.rule.TableModifyConverterRule;
 import 
org.apache.ignite.internal.processors.query.calcite.rule.UnionConverterRule;
@@ -40,6 +40,8 @@ import 
org.apache.ignite.internal.processors.query.calcite.rule.logical.FilterJo
 import 
org.apache.ignite.internal.processors.query.calcite.rule.logical.LogicalFilterMergeRule;
 import 
org.apache.ignite.internal.processors.query.calcite.rule.logical.LogicalFilterProjectTransposeRule;
 import 
org.apache.ignite.internal.processors.query.calcite.rule.logical.LogicalOrToUnionRule;
+import 
org.apache.ignite.internal.processors.query.calcite.rule.logical.LogicalProjectMergeRule;
+import 
org.apache.ignite.internal.processors.query.calcite.rule.logical.LogicalProjectRemoveRule;
 
 import static 
org.apache.ignite.internal.processors.query.calcite.prepare.IgnitePrograms.cbo;
 import static 
org.apache.ignite.internal.processors.query.calcite.prepare.IgnitePrograms.hep;
@@ -77,13 +79,16 @@ public enum PlannerPhase {
                 FilterJoinRule.PUSH_JOIN_CONDITION,
                 FilterJoinRule.FILTER_ON_JOIN,
                 ProjectConverterRule.INSTANCE,
-                ProjectMergeRule.INSTANCE,
+                LogicalProjectMergeRule.INSTANCE,
+                LogicalProjectRemoveRule.INSTANCE,
+                ProjectScanMergeRule.TABLE_SCAN,
+                ProjectScanMergeRule.INDEX_SCAN,
                 FilterConverterRule.INSTANCE,
                 LogicalFilterMergeRule.INSTANCE,
                 LogicalFilterProjectTransposeRule.INSTANCE,
+                FilterScanMergeRule.TABLE_SCAN,
+                FilterScanMergeRule.INDEX_SCAN,
                 TableModifyConverterRule.INSTANCE,
-                PushFilterIntoScanRule.FILTER_INTO_INDEX_SCAN,
-                PushFilterIntoScanRule.FILTER_INTO_TABLE_SCAN,
                 ProjectFilterTransposeRule.INSTANCE,
                 LogicalOrToUnionRule.INSTANCE,
                 UnionMergeRule.INSTANCE,
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/FilterableTableScan.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/FilterableTableScan.java
deleted file mode 100644
index 87a933a..0000000
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/FilterableTableScan.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package org.apache.ignite.internal.processors.query.calcite.rel;
-
-import java.util.List;
-import org.apache.calcite.plan.RelOptCluster;
-import org.apache.calcite.plan.RelOptCost;
-import org.apache.calcite.plan.RelOptPlanner;
-import org.apache.calcite.plan.RelOptTable;
-import org.apache.calcite.plan.RelTraitSet;
-import org.apache.calcite.rel.RelInput;
-import org.apache.calcite.rel.RelNode;
-import org.apache.calcite.rel.RelWriter;
-import org.apache.calcite.rel.core.TableScan;
-import org.apache.calcite.rel.hint.RelHint;
-import org.apache.calcite.rel.metadata.RelMetadataQuery;
-import org.apache.calcite.rex.RexNode;
-import org.jetbrains.annotations.Nullable;
-
-public class FilterableTableScan extends TableScan {
-    /** */
-    protected final RexNode cond;
-
-    /** */
-    public FilterableTableScan(RelOptCluster cluster, RelTraitSet traitSet,
-        List<RelHint> hints, RelOptTable table, @Nullable RexNode cond) {
-        super(cluster, traitSet, hints, table);
-        this.cond = cond;
-    }
-
-    /** */
-    public FilterableTableScan(RelInput input) {
-        super(input);
-        cond = input.getExpression("filters");
-    }
-
-    /** */
-    public RexNode condition() {
-        return cond;
-    }
-
-    /** {@inheritDoc} */
-    @Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) {
-        assert inputs.isEmpty();
-
-        return this;
-    }
-
-    /** {@inheritDoc} */
-    @Override public RelWriter explainTerms(RelWriter pw) {
-        return explainTerms0(super.explainTerms(pw));
-    }
-
-    /** */
-    protected RelWriter explainTerms0(RelWriter pw) {
-        return pw.itemIf("filters", cond, cond != null);
-    }
-
-    /** {@inheritDoc} */
-    @Override public RelOptCost computeSelfCost(RelOptPlanner planner, 
RelMetadataQuery mq) {
-        double tableRows = table.getRowCount();
-        return planner.getCostFactory().makeCost(tableRows, 0, 0);
-    }
-
-    /** {@inheritDoc} */
-    @Override public double estimateRowCount(RelMetadataQuery mq) {
-        double rows = table.getRowCount();
-
-        if (cond != null)
-            rows *= mq.getSelectivity(this, cond);
-
-        return rows;
-    }
-}
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteIndexScan.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteIndexScan.java
index aa31932..daaf28b 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteIndexScan.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteIndexScan.java
@@ -49,6 +49,7 @@ import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexUtil;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable;
 import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils;
 import org.apache.ignite.internal.util.typedef.F;
@@ -67,7 +68,7 @@ import static 
org.apache.ignite.internal.processors.query.calcite.util.RexUtils.
 /**
  * Relational operator that returns the contents of a table.
  */
-public class IgniteIndexScan extends FilterableTableScan implements IgniteRel {
+public class IgniteIndexScan extends ProjectableFilterableTableScan implements 
IgniteRel {
     /** Supported index operations. */
     public static final Set<SqlKind> TREE_INDEX_COMPARISON =
         EnumSet.of(
@@ -109,16 +110,36 @@ public class IgniteIndexScan extends FilterableTableScan 
implements IgniteRel {
      * @param traits Traits of this relational expression
      * @param tbl Table definition.
      * @param idxName Index name.
-     * @param cond Filters for scan.
+     */
+    public IgniteIndexScan(
+        RelOptCluster cluster,
+        RelTraitSet traits,
+        RelOptTable tbl,
+        String idxName) {
+        this(cluster, traits, tbl, idxName, null, null, null);
+    }
+
+    /**
+     * Creates a TableScan.
+     * @param cluster Cluster that this relational expression belongs to
+     * @param traits Traits of this relational expression
+     * @param tbl Table definition.
+     * @param idxName Index name.
+     * @param proj Projects.
+     * @param cond Filters.
+     * @param requiredColunms Participating colunms.
      */
     public IgniteIndexScan(
         RelOptCluster cluster,
         RelTraitSet traits,
         RelOptTable tbl,
         String idxName,
-        @Nullable RexNode cond
+        @Nullable List<RexNode> proj,
+        @Nullable RexNode cond,
+        @Nullable ImmutableBitSet requiredColunms
     ) {
-        super(cluster, traits, ImmutableList.of(), tbl, cond);
+        super(cluster, traits, ImmutableList.of(), tbl, proj, cond,
+            requiredColunms);
 
         this.idxName = idxName;
         RelCollation coll = TraitUtils.collation(traits);
@@ -135,7 +156,7 @@ public class IgniteIndexScan extends FilterableTableScan 
implements IgniteRel {
         if (!boundsArePossible())
             return;
 
-        assert cond != null;
+        assert condition() != null;
 
         Map<Integer, List<RexCall>> fieldsToPredicates = 
mapPredicatesToFields();
 
@@ -222,7 +243,7 @@ public class IgniteIndexScan extends FilterableTableScan 
implements IgniteRel {
 
     /** */
     private Map<Integer, List<RexCall>> mapPredicatesToFields() {
-        List<RexNode> predicatesConjunction = RelOptUtil.conjunctions(cond);
+        List<RexNode> predicatesConjunction = 
RelOptUtil.conjunctions(condition());
 
         Map<Integer, List<RexCall>> fieldsToPredicates = new 
HashMap<>(predicatesConjunction.size());
 
@@ -252,10 +273,10 @@ public class IgniteIndexScan extends FilterableTableScan 
implements IgniteRel {
 
     /** */
     private boolean boundsArePossible() {
-        if (cond == null)
+        if (condition() == null)
             return false;
 
-        RexCall dnf = (RexCall)RexUtil.toDnf(getCluster().getRexBuilder(), 
cond);
+        RexCall dnf = (RexCall)RexUtil.toDnf(getCluster().getRexBuilder(), 
condition());
 
         if (dnf.isA(OR) && dnf.getOperands().size() > 1) // OR conditions are 
not supported yet.
             return false;
@@ -354,9 +375,11 @@ public class IgniteIndexScan extends FilterableTableScan 
implements IgniteRel {
 
     /** {@inheritDoc} */
     @Override protected RelWriter explainTerms0(RelWriter pw) {
-        return pw.item("index", idxName )
-            .item("collation", collation)
-            .itemIf("filters", cond, cond != null)
+        pw = pw
+            .item("index", idxName)
+            .item("collation", collation);
+        pw = super.explainTerms0(pw);
+        return pw
             .itemIf("lower", lowerIdxCond, !F.isEmpty(lowerIdxCond))
             .itemIf("upper", upperIdxCond, !F.isEmpty(upperIdxCond));
     }
@@ -377,6 +400,9 @@ public class IgniteIndexScan extends FilterableTableScan 
implements IgniteRel {
     @Override public RelOptCost computeSelfCost(RelOptPlanner planner, 
RelMetadataQuery mq) {
         double tableRows = table.getRowCount() * idxSelectivity;
 
+        if (projects() != null)
+            tableRows += tableRows * projects().size();
+
         tableRows = RelMdUtil.addEpsilon(tableRows);
 
         return planner.getCostFactory().makeCost(tableRows, 0, 0);
@@ -386,8 +412,8 @@ public class IgniteIndexScan extends FilterableTableScan 
implements IgniteRel {
     @Override public double estimateRowCount(RelMetadataQuery mq) {
         double rows = table.getRowCount() * idxSelectivity;
 
-        if (cond != null)
-            rows *= mq.getSelectivity(this, cond);
+        if (condition() != null)
+            rows *= mq.getSelectivity(this, condition());
 
         return rows;
     }
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteProject.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteProject.java
index 5864bcf..1fd1e56 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteProject.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteProject.java
@@ -21,10 +21,11 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-
 import com.google.common.collect.ImmutableList;
 import org.apache.calcite.linq4j.Ord;
 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.RelCollation;
 import org.apache.calcite.rel.RelCollations;
@@ -32,6 +33,8 @@ import org.apache.calcite.rel.RelFieldCollation;
 import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Project;
+import org.apache.calcite.rel.metadata.RelMdUtil;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rex.RexInputRef;
 import org.apache.calcite.rex.RexNode;
@@ -109,6 +112,8 @@ public class IgniteProject extends Project implements 
TraitsAwareIgniteRel {
         ImmutableIntList keys = distribution.getKeys();
         List<Integer> srcKeys = new ArrayList<>(keys.size());
 
+        distribution = distribution.apply(mapping);
+
         for (int key : keys) {
             int src = mapping.getSourceOpt(key);
 
@@ -170,47 +175,28 @@ public class IgniteProject extends Project implements 
TraitsAwareIgniteRel {
 
     /** {@inheritDoc} */
     @Override public List<Pair<RelTraitSet, List<RelTraitSet>>> 
deriveDistribution(RelTraitSet nodeTraits, List<RelTraitSet> inputTraits) {
-        // All distribution types except hash distribution are propagated as 
is.
-        // In case of hash distribution we need to project distribution keys.
-        // In case one of distribution keys is erased by projection result 
distribution
-        // becomes random since we cannot determine where data is without 
erased key.
-
         RelTraitSet in = inputTraits.get(0);
-        IgniteDistribution distribution = TraitUtils.distribution(in);
-
-        if (distribution.getType() == HASH_DISTRIBUTED) {
-            Mappings.TargetMapping mapping = Project.getPartialMapping(
-                input.getRowType().getFieldCount(), getProjects());
 
-            return 
ImmutableList.of(Pair.of(nodeTraits.replace(distribution.apply(mapping)), 
ImmutableList.of(in)));
-        }
+        IgniteDistribution distribution = TraitUtils.projectDistribution(
+            TraitUtils.distribution(in), getProjects(), 
getInput().getRowType());
 
         return ImmutableList.of(Pair.of(nodeTraits.replace(distribution), 
ImmutableList.of(in)));
     }
 
     /** {@inheritDoc} */
     @Override public List<Pair<RelTraitSet, List<RelTraitSet>>> 
deriveCollation(RelTraitSet nodeTraits, List<RelTraitSet> inputTraits) {
-        // The code below projects input collation.
-
         RelTraitSet in = inputTraits.get(0);
-        RelCollation collation = TraitUtils.collation(in);
 
-        if (collation.getFieldCollations().isEmpty())
-            return 
ImmutableList.of(Pair.of(nodeTraits.replace(RelCollations.EMPTY), 
ImmutableList.of(in)));
+        RelCollation collation = TraitUtils.projectCollation(
+            TraitUtils.collation(in), getProjects(), getInput().getRowType());
 
-        Map<Integer, Integer> targets = new HashMap<>();
-        for (Ord<RexNode> project : Ord.zip(getProjects())) {
-            if (project.e instanceof RexInputRef)
-                targets.putIfAbsent(((RexInputRef)project.e).getIndex(), 
project.i);
-        }
-
-        List<RelFieldCollation> outFieldCollations = new ArrayList<>();
-        for (RelFieldCollation inFieldCollation : 
collation.getFieldCollations()) {
-            Integer newIndex = targets.get(inFieldCollation.getFieldIndex());
-            if (newIndex != null)
-                
outFieldCollations.add(inFieldCollation.withFieldIndex(newIndex));
-        }
+        return ImmutableList.of(Pair.of(nodeTraits.replace(collation), 
ImmutableList.of(in)));
+    }
 
-        return 
ImmutableList.of(Pair.of(nodeTraits.replace(RelCollations.of(outFieldCollations)),
 ImmutableList.of(in)));
+    /** {@inheritDoc} */
+    @Override public RelOptCost computeSelfCost(RelOptPlanner planner, 
RelMetadataQuery mq) {
+        double rowCount = mq.getRowCount(getInput()) * exps.size();
+        rowCount = RelMdUtil.addEpsilon(rowCount); // to differ from rel nodes 
with integrated projection
+        return planner.getCostFactory().makeCost(rowCount, 0, 0);
     }
 }
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTableScan.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTableScan.java
index a25623e..14734f2 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTableScan.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/IgniteTableScan.java
@@ -17,12 +17,14 @@
 
 package org.apache.ignite.internal.processors.query.calcite.rel;
 
+import java.util.List;
 import com.google.common.collect.ImmutableList;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.util.ImmutableBitSet;
 import org.jetbrains.annotations.Nullable;
 
 import static 
org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils.changeTraits;
@@ -30,7 +32,7 @@ import static 
org.apache.ignite.internal.processors.query.calcite.trait.TraitUti
 /**
  * Relational operator that returns the contents of a table.
  */
-public class IgniteTableScan extends FilterableTableScan implements IgniteRel {
+public class IgniteTableScan extends ProjectableFilterableTableScan implements 
IgniteRel {
     /**
      * Constructor used for deserialization.
      *
@@ -49,9 +51,29 @@ public class IgniteTableScan extends FilterableTableScan 
implements IgniteRel {
     public IgniteTableScan(
         RelOptCluster cluster,
         RelTraitSet traits,
+        RelOptTable tbl
+    ) {
+        super(cluster, traits, ImmutableList.of(), tbl);
+    }
+
+    /**
+     * Creates a TableScan.
+     * @param cluster Cluster that this relational expression belongs to
+     * @param traits Traits of this relational expression
+     * @param tbl Table definition.
+     * @param proj Projects.
+     * @param cond Filters.
+     * @param requiredColunms Participating colunms.
+     */
+    public IgniteTableScan(
+        RelOptCluster cluster,
+        RelTraitSet traits,
         RelOptTable tbl,
-        @Nullable RexNode cond) {
-        super(cluster, traits, ImmutableList.of(), tbl, cond);
+        @Nullable List<RexNode> proj,
+        @Nullable RexNode cond,
+        @Nullable ImmutableBitSet requiredColunms
+    ) {
+        super(cluster, traits, ImmutableList.of(), tbl, proj, cond, 
requiredColunms);
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/ProjectableFilterableTableScan.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/ProjectableFilterableTableScan.java
new file mode 100644
index 0000000..97e068d
--- /dev/null
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/ProjectableFilterableTableScan.java
@@ -0,0 +1,136 @@
+package org.apache.ignite.internal.processors.query.calcite.rel;
+
+import java.util.List;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelOptCost;
+import org.apache.calcite.plan.RelOptPlanner;
+import org.apache.calcite.plan.RelOptTable;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelInput;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.RelWriter;
+import org.apache.calcite.rel.core.TableScan;
+import org.apache.calcite.rel.hint.RelHint;
+import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexUtil;
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.jetbrains.annotations.Nullable;
+
+/** Scan with projects and filters. */
+public class ProjectableFilterableTableScan extends TableScan {
+    /** Filters. */
+    private final RexNode condition;
+
+    /** Projects. */
+    private final List<RexNode> projects;
+
+    /** Participating colunms. */
+    private final ImmutableBitSet requiredColunms;
+
+    /** */
+    public ProjectableFilterableTableScan(
+        RelOptCluster cluster,
+        RelTraitSet traitSet,
+        List<RelHint> hints,
+        RelOptTable tbl
+    ) {
+        this(cluster, traitSet, hints, tbl, null, null, null);
+    }
+
+    /** */
+    public ProjectableFilterableTableScan(
+        RelOptCluster cluster,
+        RelTraitSet traitSet,
+        List<RelHint> hints,
+        RelOptTable table,
+        @Nullable List<RexNode> proj,
+        @Nullable RexNode cond,
+        @Nullable ImmutableBitSet reqColunms
+    ) {
+        super(cluster, traitSet, hints, table);
+
+        projects = proj;
+        condition = cond;
+        requiredColunms = reqColunms;
+    }
+
+    /** */
+    public ProjectableFilterableTableScan(RelInput input) {
+        super(input);
+        condition = input.getExpression("filters");
+        projects = input.get("projects") == null ? null : 
input.getExpressionList("projects");
+        requiredColunms = input.get("requiredColunms") == null ? null : 
input.getBitSet("requiredColunms");
+    }
+
+    /** @return Projections. */
+    public List<RexNode> projects() {
+        return projects;
+    }
+
+    /** @return Rex condition. */
+    public RexNode condition() {
+        return condition;
+    }
+
+    /** @return Participating colunms. */
+    public ImmutableBitSet requiredColunms() {
+        return requiredColunms;
+    }
+
+    /** {@inheritDoc} */
+    @Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) {
+        assert inputs.isEmpty();
+
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override public RelWriter explainTerms(RelWriter pw) {
+        return explainTerms0(super.explainTerms(pw));
+    }
+
+    /** */
+    protected RelWriter explainTerms0(RelWriter pw) {
+        return pw
+            .itemIf("filters", condition, condition != null)
+            .itemIf("projects", projects, projects != null)
+            .itemIf("requiredColunms", requiredColunms, requiredColunms != 
null);
+    }
+
+    /** {@inheritDoc} */
+    @Override public RelOptCost computeSelfCost(RelOptPlanner planner, 
RelMetadataQuery mq) {
+        double tableRows = table.getRowCount();
+
+        if (projects != null)
+            tableRows += tableRows * projects.size();
+
+        return planner.getCostFactory().makeCost(tableRows, 0, 0);
+    }
+
+    /** {@inheritDoc} */
+    @Override public double estimateRowCount(RelMetadataQuery mq) {
+        double rows = table.getRowCount();
+
+        if (condition != null)
+            rows *= mq.getSelectivity(this, condition);
+
+        return rows;
+    }
+
+    /** {@inheritDoc} */
+    @Override public RelDataType deriveRowType() {
+        if (projects != null)
+            return 
RexUtil.createStructType(Commons.context(this).typeFactory(), projects);
+        else
+            return 
table.unwrap(IgniteTable.class).getRowType(getCluster().getTypeFactory(), 
requiredColunms);
+    }
+
+    /** */
+    public boolean simple() {
+        return condition == null && projects == null && requiredColunms == 
null;
+    }
+}
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/ExposeIndexRule.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/ExposeIndexRule.java
index 46c9407..1dccef2 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/ExposeIndexRule.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/ExposeIndexRule.java
@@ -45,8 +45,8 @@ public class ExposeIndexRule extends RelOptRule {
 
     /** */
     private static boolean preMatch(IgniteTableScan scan) {
-        return scan.getTable().unwrap(IgniteTable.class).indexes().size() > 1  
   // has indexes to expose
-            && scan.condition() == null;                                       
   // was not modified by PushFilterIntoScanRule
+        return scan.simple() // was not modified by ProjectScanMergeRule or 
FilterScanMergeRule
+            && scan.getTable().unwrap(IgniteTable.class).indexes().size() > 1; 
// has indexes to expose
     }
 
     /** {@inheritDoc} */
@@ -63,7 +63,7 @@ public class ExposeIndexRule extends RelOptRule {
 
         assert indexes.size() > 1;
 
-        Map<RelNode, RelNode> equivMap = new HashMap<>();
+        Map<RelNode, RelNode> equivMap = new HashMap<>(indexes.size() - 1);
         for (int i = 1; i < indexes.size(); i++)
             equivMap.put(indexes.get(i), scan);
 
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/PushFilterIntoScanRule.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/FilterScanMergeRule.java
similarity index 62%
rename from 
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/PushFilterIntoScanRule.java
rename to 
modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/FilterScanMergeRule.java
index ca30dbe..e5f071a 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/PushFilterIntoScanRule.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/FilterScanMergeRule.java
@@ -17,7 +17,9 @@
 package org.apache.ignite.internal.processors.query.calcite.rule;
 
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
+import org.apache.calcite.linq4j.Ord;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelOptRuleCall;
@@ -25,15 +27,22 @@ import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.RelFactories;
 import org.apache.calcite.rel.logical.LogicalFilter;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
+import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rex.RexInputRef;
 import org.apache.calcite.rex.RexLocalRef;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexShuttle;
 import org.apache.calcite.rex.RexSimplify;
 import org.apache.calcite.rex.RexUtil;
-import 
org.apache.ignite.internal.processors.query.calcite.rel.FilterableTableScan;
+import org.apache.calcite.util.ControlFlowException;
+import org.apache.calcite.util.mapping.MappingType;
+import org.apache.calcite.util.mapping.Mappings;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
+import 
org.apache.ignite.internal.processors.query.calcite.rel.ProjectableFilterableTableScan;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable;
+import 
org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
 import org.apache.ignite.internal.util.typedef.F;
 
 import static 
org.apache.ignite.internal.processors.query.calcite.util.RexUtils.builder;
@@ -42,22 +51,23 @@ import static 
org.apache.ignite.internal.processors.query.calcite.util.RexUtils.
 /**
  * Rule that pushes filter into the scan. This might be useful for index range 
scans.
  */
-public abstract class PushFilterIntoScanRule<T extends FilterableTableScan> 
extends RelOptRule {
+public abstract class FilterScanMergeRule<T extends 
ProjectableFilterableTableScan> extends RelOptRule {
     /** Instance. */
-    public static final PushFilterIntoScanRule<IgniteIndexScan> 
FILTER_INTO_INDEX_SCAN =
-        new PushFilterIntoScanRule<IgniteIndexScan>(LogicalFilter.class, 
IgniteIndexScan.class, "PushFilterIntoIndexScanRule") {
+    public static final FilterScanMergeRule<IgniteIndexScan> INDEX_SCAN =
+        new FilterScanMergeRule<IgniteIndexScan>(LogicalFilter.class, 
IgniteIndexScan.class, "FilterIndexScanMergeRule") {
             /** {@inheritDoc} */
             @Override protected IgniteIndexScan createNode(RelOptCluster 
cluster, IgniteIndexScan scan, RexNode cond) {
-                return new IgniteIndexScan(cluster, scan.getTraitSet(), 
scan.getTable(), scan.indexName(), cond);
+                return new IgniteIndexScan(cluster, scan.getTraitSet(), 
scan.getTable(), scan.indexName(),
+                    scan.projects(), cond, scan.requiredColunms());
             }
         };
 
     /** Instance. */
-    public static final PushFilterIntoScanRule<IgniteTableScan> 
FILTER_INTO_TABLE_SCAN =
-        new PushFilterIntoScanRule<IgniteTableScan>(LogicalFilter.class, 
IgniteTableScan.class, "PushFilterIntoTableScanRule") {
+    public static final FilterScanMergeRule<IgniteTableScan> TABLE_SCAN =
+        new FilterScanMergeRule<IgniteTableScan>(LogicalFilter.class, 
IgniteTableScan.class, "FilterTableScanMergeRule") {
             /** {@inheritDoc} */
             @Override protected IgniteTableScan createNode(RelOptCluster 
cluster, IgniteTableScan scan, RexNode cond) {
-                return new IgniteTableScan(cluster, scan.getTraitSet(), 
scan.getTable(), cond);
+                return new IgniteTableScan(cluster, scan.getTraitSet(), 
scan.getTable(), scan.projects(), cond, scan.requiredColunms());
             }
         };
 
@@ -67,7 +77,7 @@ public abstract class PushFilterIntoScanRule<T extends 
FilterableTableScan> exte
      * @param clazz Class of relational expression to match.
      * @param desc Description, or null to guess description
      */
-    private PushFilterIntoScanRule(Class<? extends RelNode> clazz, Class<T> 
tableClass, String desc) {
+    private FilterScanMergeRule(Class<? extends RelNode> clazz, Class<T> 
tableClass, String desc) {
         super(operand(clazz,
             operand(tableClass, none())),
             RelFactories.LOGICAL_BUILDER,
@@ -84,6 +94,32 @@ public abstract class PushFilterIntoScanRule<T extends 
FilterableTableScan> exte
 
         RexNode cond = filter.getCondition();
 
+        if (scan.projects() != null) {
+            IgniteTypeFactory typeFactory = 
Commons.context(scan).typeFactory();
+
+            IgniteTable tbl = scan.getTable().unwrap(IgniteTable.class);
+
+            RelDataType cols = tbl.getRowType(typeFactory, 
scan.requiredColunms());
+
+            Mappings.TargetMapping permutation = permutation(scan.projects(), 
cols.getFieldCount());
+
+            try {
+                cond = new RexShuttle() {
+                    @Override public RexNode visitLocalRef(RexLocalRef ref) {
+                        int targetRef = 
permutation.getTargetOpt(ref.getIndex());
+
+                        if (targetRef == -1)
+                            throw new ControlFlowException();
+
+                        return new RexLocalRef(targetRef, ref.getType());
+                    }
+                }.apply(cond);
+            }
+            catch (ControlFlowException e) {
+                return;
+            }
+        }
+
         RexSimplify simplifier = simplifier(cluster);
 
         // Let's remove from the condition common with the scan filter parts.
@@ -117,4 +153,16 @@ public abstract class PushFilterIntoScanRule<T extends 
FilterableTableScan> exte
             return new RexLocalRef(inputRef.getIndex(), inputRef.getType());
         }
     }
+
+    /** */
+    private static Mappings.TargetMapping permutation(List<RexNode> nodes, int 
totalSize) {
+        final Mappings.TargetMapping mapping =
+            Mappings.create(MappingType.PARTIAL_FUNCTION, nodes.size(), 
totalSize);
+
+        for (Ord<RexNode> node : Ord.zip(nodes)) {
+            if (node.e instanceof RexLocalRef)
+                mapping.set(node.i, ((RexLocalRef) node.e).getIndex());
+        }
+        return mapping;
+    }
 }
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/ProjectScanMergeRule.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/ProjectScanMergeRule.java
new file mode 100644
index 0000000..d7eebd9
--- /dev/null
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/ProjectScanMergeRule.java
@@ -0,0 +1,153 @@
+/*
+ * 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 java.util.List;
+import org.apache.calcite.linq4j.Ord;
+import org.apache.calcite.plan.RelOptCluster;
+import org.apache.calcite.plan.RelOptRule;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.plan.RelTraitSet;
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.logical.LogicalProject;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexLocalRef;
+import org.apache.calcite.rex.RexNode;
+import org.apache.calcite.rex.RexShuttle;
+import org.apache.calcite.util.ImmutableBitSet;
+import org.apache.calcite.util.mapping.MappingType;
+import org.apache.calcite.util.mapping.Mappings;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
+import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
+import 
org.apache.ignite.internal.processors.query.calcite.rel.ProjectableFilterableTableScan;
+import org.apache.ignite.internal.processors.query.calcite.schema.IgniteTable;
+import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils;
+import 
org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+
+import static 
org.apache.ignite.internal.processors.query.calcite.util.RexUtils.isIdentity;
+
+/** */
+public abstract class ProjectScanMergeRule<T extends 
ProjectableFilterableTableScan> extends RelOptRule {
+    /** Instance. */
+    public static final ProjectScanMergeRule<IgniteIndexScan> INDEX_SCAN =
+        new ProjectScanMergeRule<IgniteIndexScan>(LogicalProject.class, 
IgniteIndexScan.class, "ProjectIndexScanMergeRule") {
+            /** {@inheritDoc} */
+            @Override protected IgniteIndexScan createNode(RelOptCluster 
cluster, IgniteIndexScan scan,
+                RelTraitSet traits, List<RexNode> projections, RexNode cond, 
ImmutableBitSet requiredColunms) {
+                return new IgniteIndexScan(cluster, traits, scan.getTable(), 
scan.indexName(), projections,
+                    cond, requiredColunms);
+            }
+        };
+
+    /** Instance. */
+    public static final ProjectScanMergeRule<IgniteTableScan> TABLE_SCAN =
+        new ProjectScanMergeRule<IgniteTableScan>(LogicalProject.class, 
IgniteTableScan.class, "ProjectTableScanMergeRule") {
+            /** {@inheritDoc} */
+            @Override protected IgniteTableScan createNode(RelOptCluster 
cluster, IgniteTableScan scan,
+                RelTraitSet traits, List<RexNode> projections, RexNode cond, 
ImmutableBitSet requiredColunms) {
+                return new IgniteTableScan(cluster, traits, scan.getTable(), 
projections, cond, requiredColunms);
+            }
+        };
+
+    /** */
+    protected abstract T createNode(RelOptCluster cluster, T scan, RelTraitSet 
traits, List<RexNode> projections,
+                                    RexNode cond, ImmutableBitSet 
requiredColunms);
+
+    /**
+     * Constructor.
+     *
+     * @param projectionClazz Projection class of relational expression to 
match.
+     * @param tableClass Ignite scan class.
+     * @param desc Description, or null to guess description
+     */
+    private ProjectScanMergeRule(Class<? extends RelNode> projectionClazz, 
Class<T> tableClass, String desc) {
+        super(operand(projectionClazz,
+                operand(tableClass, none())),
+                    RelFactories.LOGICAL_BUILDER, desc);
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean matches(RelOptRuleCall call) {
+        T rel = call.rel(1);
+        return rel.projects() == null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onMatch(RelOptRuleCall call) {
+        LogicalProject relProject = call.rel(0);
+        T scan = call.rel(1);
+
+        RelOptCluster cluster = scan.getCluster();
+        List<RexNode> projects = relProject.getProjects();
+        RexNode cond = scan.condition();
+
+        // projection changes input collation and distribution.
+        RelTraitSet traits = scan.getTraitSet();
+
+        traits = traits.replace(TraitUtils.projectCollation(
+            TraitUtils.collation(traits), projects, scan.getRowType()));
+
+        traits = traits.replace(TraitUtils.projectDistribution(
+            TraitUtils.distribution(traits), projects, scan.getRowType()));
+
+        IgniteTable tbl = scan.getTable().unwrap(IgniteTable.class);
+        IgniteTypeFactory typeFactory = Commons.context(scan).typeFactory();
+        ImmutableBitSet.Builder builder = ImmutableBitSet.builder();
+
+        new RexShuttle() {
+            @Override public RexNode visitInputRef(RexInputRef ref) {
+                builder.set(ref.getIndex());
+                return ref;
+            }
+        }.apply(projects);
+
+        new RexShuttle() {
+            @Override public RexNode visitLocalRef(RexLocalRef inputRef) {
+                builder.set(inputRef.getIndex());
+                return inputRef;
+            }
+        }.apply(cond);
+
+        ImmutableBitSet requiredColunms = builder.build();
+
+        Mappings.TargetMapping targetMapping = 
Mappings.create(MappingType.PARTIAL_FUNCTION,
+            tbl.getRowType(typeFactory).getFieldCount(), 
requiredColunms.cardinality());
+
+        for (Ord<Integer> ord : Ord.zip(requiredColunms))
+            targetMapping.set(ord.e, ord.i);
+
+        projects = new RexShuttle() {
+            @Override public RexNode visitInputRef(RexInputRef ref) {
+                return new 
RexLocalRef(targetMapping.getTarget(ref.getIndex()), ref.getType());
+            }
+        }.apply(projects);
+
+        if (isIdentity(projects, tbl.getRowType(typeFactory, requiredColunms), 
true))
+            projects = null;
+
+        cond = new RexShuttle() {
+            @Override public RexNode visitLocalRef(RexLocalRef ref) {
+                return new 
RexLocalRef(targetMapping.getTarget(ref.getIndex()), ref.getType());
+            }
+        }.apply(cond);
+
+        call.transformTo(createNode(cluster, scan, traits, projects, cond, 
requiredColunms));
+    }
+}
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/LogicalProjectMergeRule.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/LogicalProjectMergeRule.java
new file mode 100644
index 0000000..be69085
--- /dev/null
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/LogicalProjectMergeRule.java
@@ -0,0 +1,47 @@
+/*
+ * 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.logical;
+
+import org.apache.calcite.plan.RelOptRule;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.logical.LogicalProject;
+import org.apache.calcite.rel.rules.ProjectMergeRule;
+import org.apache.calcite.rel.rules.TransformationRule;
+
+/**
+ * LogicalProjectMergeRule merges a {@link LogicalProject} into another {@link 
LogicalProject},
+ * provided the projects aren't projecting identical sets of input references.
+ */
+public class LogicalProjectMergeRule extends RelOptRule implements 
TransformationRule {
+    /** */
+    public static final RelOptRule INSTANCE = new LogicalProjectMergeRule();
+
+    /** */
+    private LogicalProjectMergeRule() {
+        super(
+            operand(LogicalProject.class,
+                operand(LogicalProject.class, any())),
+            RelFactories.LOGICAL_BUILDER, null);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onMatch(RelOptRuleCall call) {
+        ProjectMergeRule.INSTANCE.onMatch(call);
+    }
+}
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/LogicalProjectRemoveRule.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/LogicalProjectRemoveRule.java
new file mode 100644
index 0000000..aacb848
--- /dev/null
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/logical/LogicalProjectRemoveRule.java
@@ -0,0 +1,49 @@
+/*
+ * 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.logical;
+
+import org.apache.calcite.plan.RelOptRule;
+import org.apache.calcite.plan.RelOptRuleCall;
+import org.apache.calcite.rel.core.RelFactories;
+import org.apache.calcite.rel.logical.LogicalProject;
+import org.apache.calcite.rel.rules.ProjectRemoveRule;
+import org.apache.calcite.rel.rules.SubstitutionRule;
+
+/**
+ * Rule that, given a {@link LogicalProject} node that
+*  merely returns its input, converts the node into its child.
+*/
+public class LogicalProjectRemoveRule extends RelOptRule implements 
SubstitutionRule {
+    /** */
+    public static final RelOptRule INSTANCE = new LogicalProjectRemoveRule();
+
+    /** Constructor. */
+    public LogicalProjectRemoveRule() {
+        super(operandJ(LogicalProject.class, null, 
ProjectRemoveRule::isTrivial, any()), RelFactories.LOGICAL_BUILDER, null);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onMatch(RelOptRuleCall call) {
+        ProjectRemoveRule.INSTANCE.onMatch(call);
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean autoPruneOld() {
+        return true;
+    }
+}
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteIndex.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteIndex.java
index 7723337..4f3ba69 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteIndex.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteIndex.java
@@ -16,13 +16,16 @@
  */
 package org.apache.ignite.internal.processors.query.calcite.schema;
 
+import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.ignite.internal.processors.query.GridIndex;
 import 
org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
 import org.apache.ignite.internal.processors.query.calcite.exec.IndexScan;
 import org.apache.ignite.internal.processors.query.h2.opt.H2Row;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Ignite scannable index.
@@ -73,7 +76,11 @@ public class IgniteIndex {
         ExecutionContext<Row> execCtx,
         Predicate<Row> filters,
         Supplier<Row> lowerIdxConditions,
-        Supplier<Row> upperIdxConditions) {
-        return new IndexScan<>(execCtx, table().descriptor(), idx, filters, 
lowerIdxConditions, upperIdxConditions);
+        Supplier<Row> upperIdxConditions,
+        Function<Row, Row> rowTransformer,
+        @Nullable ImmutableBitSet requiredColunms
+    ) {
+        return new IndexScan<>(
+            execCtx, table().descriptor(), idx, filters, lowerIdxConditions, 
upperIdxConditions, rowTransformer, requiredColunms);
     }
 }
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteTable.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteTable.java
index def8848..04ab200 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteTable.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteTable.java
@@ -17,17 +17,22 @@
 package org.apache.ignite.internal.processors.query.calcite.schema;
 
 import java.util.Map;
+import java.util.function.Function;
 import java.util.function.Predicate;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.rel.core.TableScan;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.schema.TranslatableTable;
+import org.apache.calcite.util.ImmutableBitSet;
 import 
org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
 import 
org.apache.ignite.internal.processors.query.calcite.metadata.NodesMapping;
 import 
org.apache.ignite.internal.processors.query.calcite.prepare.PlanningContext;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
 import 
org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Ignite table.
@@ -39,6 +44,19 @@ public interface IgniteTable extends TranslatableTable {
     TableDescriptor descriptor();
 
     /** {@inheritDoc} */
+    default @Override RelDataType getRowType(RelDataTypeFactory typeFactory) {
+        return getRowType(typeFactory, null);
+    }
+
+    /**
+     * Returns new type according {@code usedClumns} param.
+     *
+     * @param typeFactory Factory.
+     * @param usedColumns Used columns enumeration.
+     */
+    RelDataType getRowType(RelDataTypeFactory typeFactory, ImmutableBitSet 
usedColumns);
+
+    /** {@inheritDoc} */
     @Override default TableScan toRel(RelOptTable.ToRelContext context, 
RelOptTable relOptTable) {
         return toRel(context.getCluster(), relOptTable);
     }
@@ -67,9 +85,15 @@ public interface IgniteTable extends TranslatableTable {
      *
      * @param execCtx Execution context.
      * @param filter
+     * @param rowTransformer Row transformer.
+     * @param usedColumns Used columns enumeration.
      * @return Rows iterator.
      */
-    public <Row> Iterable<Row> scan(ExecutionContext<Row> execCtx, 
Predicate<Row> filter);
+    public <Row> Iterable<Row> scan(
+        ExecutionContext<Row> execCtx,
+        Predicate<Row> filter,
+        Function<Row, Row> rowTransformer,
+        @Nullable ImmutableBitSet usedColumns);
 
     /**
      * Returns nodes mapping.
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteTableImpl.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteTableImpl.java
index d52d38a..cbfc554 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteTableImpl.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteTableImpl.java
@@ -18,10 +18,12 @@
 package org.apache.ignite.internal.processors.query.calcite.schema;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
 import java.util.function.Predicate;
 import com.google.common.collect.ImmutableList;
 import org.apache.calcite.plan.RelOptCluster;
@@ -50,10 +52,12 @@ import 
org.apache.ignite.internal.processors.query.calcite.rel.IgniteIndexScan;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteTableScan;
 import 
org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistribution;
 import 
org.apache.ignite.internal.processors.query.calcite.trait.RewindabilityTrait;
+import 
org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
 import org.apache.ignite.internal.processors.query.calcite.util.Commons;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import static java.util.Collections.emptyList;
 import static java.util.Collections.singletonList;
@@ -82,8 +86,8 @@ public class IgniteTableImpl extends AbstractTable implements 
IgniteTable {
     }
 
     /** {@inheritDoc} */
-    @Override public RelDataType getRowType(RelDataTypeFactory typeFactory) {
-        return desc.apply(typeFactory);
+    @Override public RelDataType getRowType(RelDataTypeFactory typeFactory, 
ImmutableBitSet usedColumns) {
+        return desc.rowType((IgniteTypeFactory)typeFactory, usedColumns);
     }
 
     /** {@inheritDoc} */
@@ -103,7 +107,7 @@ public class IgniteTableImpl extends AbstractTable 
implements IgniteTable {
             .replace(distribution())
             .replace(RewindabilityTrait.REWINDABLE);
 
-        return new IgniteTableScan(cluster, traitSet, relOptTbl, null);
+        return new IgniteTableScan(cluster, traitSet, relOptTbl);
     }
 
     /** {@inheritDoc} */
@@ -113,12 +117,17 @@ public class IgniteTableImpl extends AbstractTable 
implements IgniteTable {
             .replace(RewindabilityTrait.REWINDABLE)
             .replace(getIndex(idxName).collation());
 
-        return new IgniteIndexScan(cluster, traitSet, relOptTbl, idxName, 
null);
+        return new IgniteIndexScan(cluster, traitSet, relOptTbl, idxName);
     }
 
     /** {@inheritDoc} */
-    @Override public <Row> Iterable<Row> scan(ExecutionContext<Row> execCtx, 
Predicate<Row> filter) {
-        return new TableScan<>(execCtx, desc, filter);
+    @Override public <Row> Iterable<Row> scan(
+        ExecutionContext<Row> execCtx,
+        Predicate<Row> filter,
+        Function<Row, Row> rowTransformer,
+        @Nullable ImmutableBitSet usedColumns
+    ) {
+        return new TableScan<>(execCtx, desc, filter, rowTransformer, 
usedColumns);
     }
 
     /** {@inheritDoc} */
@@ -141,16 +150,14 @@ public class IgniteTableImpl extends AbstractTable 
implements IgniteTable {
         }
     }
 
-
     /** {@inheritDoc} */
     @Override public IgniteDistribution distribution() {
         return desc.distribution();
     }
 
     /** {@inheritDoc} */
-    @SuppressWarnings("AssignmentOrReturnOfFieldWithMutableType")
     @Override public Map<String, IgniteIndex> indexes() {
-        return indexes;
+        return Collections.unmodifiableMap(indexes);
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/TableDescriptor.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/TableDescriptor.java
index eef32fc..00a3958 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/TableDescriptor.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/TableDescriptor.java
@@ -18,12 +18,13 @@
 package org.apache.ignite.internal.processors.query.calcite.schema;
 
 import java.util.Map;
-
 import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.rel.core.TableModify;
 import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rel.type.RelProtoDataType;
 import org.apache.calcite.sql2rel.InitializerExpressionFactory;
+import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.processors.cache.GridCacheContext;
 import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
@@ -49,6 +50,11 @@ public interface TableDescriptor extends RelProtoDataType, 
InitializerExpression
      */
     IgniteDistribution distribution();
 
+    /** {@inheritDoc} */
+    @Override default RelDataType apply(RelDataTypeFactory factory) {
+        return rowType((IgniteTypeFactory)factory, null);
+    }
+
     /**
      * Returns row type excluding effectively virtual fields.
      *
@@ -56,7 +62,7 @@ public interface TableDescriptor extends RelProtoDataType, 
InitializerExpression
      * @return Row type for INSERT operation.
      */
     default RelDataType insertRowType(IgniteTypeFactory factory) {
-        return apply(factory);
+        return rowType(factory, null);
     }
 
     /**
@@ -66,10 +72,19 @@ public interface TableDescriptor extends RelProtoDataType, 
InitializerExpression
      * @return Row type for SELECT operation.
      */
     default RelDataType selectRowType(IgniteTypeFactory factory) {
-        return apply(factory);
+        return rowType(factory, null);
     }
 
     /**
+     * Returns row type.
+     *
+     * @param factory Type factory.
+     * @param usedColumns Participating columns numeration.
+     * @return Row type.
+     */
+    RelDataType rowType(IgniteTypeFactory factory, ImmutableBitSet 
usedColumns);
+
+    /**
      * Checks whether is possible to update a column with a given index.
      *
      * @param tbl Parent table.
@@ -91,10 +106,12 @@ public interface TableDescriptor extends RelProtoDataType, 
InitializerExpression
      *
      * @param ectx Execution context.
      * @param row Cache row.
+     * @param requiredColunms Participating colunms.
      * @return Relational node row.
      * @throws IgniteCheckedException If failed.
      */
-    <Row> Row toRow(ExecutionContext<Row> ectx, CacheDataRow row, 
RowHandler.RowFactory<Row> factory) throws IgniteCheckedException;
+    <Row> Row toRow(ExecutionContext<Row> ectx, CacheDataRow row, 
RowHandler.RowFactory<Row> factory,
+                    @Nullable ImmutableBitSet requiredColunms) throws 
IgniteCheckedException;
 
     /**
      * Converts a relational node row to cache key-value tuple;
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/TableDescriptorImpl.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/TableDescriptorImpl.java
index 9110fa4..7a7f41c 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/TableDescriptorImpl.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/TableDescriptorImpl.java
@@ -25,7 +25,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
-
 import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.rel.core.TableModify;
 import org.apache.calcite.rel.type.RelDataType;
@@ -35,6 +34,7 @@ import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.schema.ColumnStrategy;
 import org.apache.calcite.sql2rel.InitializerContext;
 import org.apache.calcite.sql2rel.NullInitializerExpressionFactory;
+import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.binary.BinaryObject;
 import org.apache.ignite.binary.BinaryObjectBuilder;
@@ -53,6 +53,7 @@ import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.lang.IgniteBiTuple;
+import org.jetbrains.annotations.Nullable;
 
 /**
  *
@@ -88,7 +89,7 @@ public class TableDescriptorImpl extends 
NullInitializerExpressionFactory
     private final int affField;
 
     /** */
-    private final BitSet virtualFlags;
+    private final ImmutableBitSet insertFields;
 
     /** */
     public TableDescriptorImpl(GridCacheContext<?,?> cctx, 
GridQueryTypeDescriptor typeDesc, Object affinityIdentity) {
@@ -101,7 +102,7 @@ public class TableDescriptorImpl extends 
NullInitializerExpressionFactory
         List<ColumnDescriptor> descriptors = new ArrayList<>(fields.size() + 
2);
 
         // A _key/_val fields is virtual in case there is an alias or a 
property(es) mapped to _key/_val object fields.
-        BitSet virtualFlags = new BitSet();
+        BitSet virtualFields = new BitSet();
 
         descriptors.add(
             new KeyValDescriptor(QueryUtils.KEY_FIELD_NAME, 
typeDesc.keyClass(), true, QueryUtils.KEY_COL));
@@ -124,7 +125,7 @@ public class TableDescriptorImpl extends 
NullInitializerExpressionFactory
 
                 keyField = descriptors.size();
 
-                virtualFlags.set(0);
+                virtualFields.set(0);
 
                 descriptors.add(new KeyValDescriptor(typeDesc.keyFieldAlias(), 
typeDesc.keyClass(), true, fldIdx++));
             }
@@ -133,12 +134,12 @@ public class TableDescriptorImpl extends 
NullInitializerExpressionFactory
 
                 descriptors.add(new 
KeyValDescriptor(typeDesc.valueFieldAlias(), typeDesc.valueClass(), false, 
fldIdx++));
 
-                virtualFlags.set(1);
+                virtualFields.set(1);
             }
             else {
                 GridQueryProperty prop = typeDesc.property(field);
 
-                virtualFlags.set(prop.key() ? 0 : 1);
+                virtualFields.set(prop.key() ? 0 : 1);
 
                 descriptors.add(new FieldDescriptor(prop, fldIdx++));
             }
@@ -151,19 +152,23 @@ public class TableDescriptorImpl extends 
NullInitializerExpressionFactory
         this.keyField = keyField;
         this.valField = valField;
         this.affField = affField;
-        this.virtualFlags = virtualFlags;
         this.descriptors = descriptors.toArray(DUMMY);
         this.descriptorsMap = descriptorsMap;
-    }
 
-    /** {@inheritDoc} */
-    @Override public RelDataType apply(RelDataTypeFactory factory) {
-        return rowType((IgniteTypeFactory) factory, false);
+        ImmutableBitSet.Builder b = ImmutableBitSet.builder();
+        for (int i = 0; i < this.descriptors.length; i++) {
+            if (virtualFields.get(i))
+                continue;
+
+            b.set(i);
+        }
+
+        insertFields = b.build();
     }
 
     /** {@inheritDoc} */
     @Override public RelDataType insertRowType(IgniteTypeFactory factory) {
-        return rowType(factory, true);
+        return rowType(factory, insertFields);
     }
 
     /** {@inheritDoc} */
@@ -185,17 +190,28 @@ public class TableDescriptorImpl extends 
NullInitializerExpressionFactory
     }
 
     /** {@inheritDoc} */
-    @Override public <Row> Row toRow(ExecutionContext<Row> ectx, CacheDataRow 
row, RowHandler.RowFactory<Row> factory) throws IgniteCheckedException {
+    @Override public <Row> Row toRow(
+        ExecutionContext<Row> ectx,
+        CacheDataRow row,
+        RowHandler.RowFactory<Row> factory,
+        @Nullable ImmutableBitSet requiredColunms
+    ) throws IgniteCheckedException {
         RowHandler<Row> handler = factory.handler();
 
         assert handler == ectx.rowHandler();
 
         Row res = factory.create();
 
-        assert handler.columnCount(res) == descriptors.length;
+        assert handler.columnCount(res) == (requiredColunms == null ? 
descriptors.length : requiredColunms.cardinality());
 
-        for (int i = 0; i < descriptors.length; i++)
-            handler.set(i, res, descriptors[i].value(ectx, cctx, row));
+        if (requiredColunms == null) {
+            for (int i = 0; i < descriptors.length; i++)
+                handler.set(i, res, descriptors[i].value(ectx, cctx, row));
+        }
+        else {
+            for (int i = 0, j = requiredColunms.nextSetBit(0); j != -1; j = 
requiredColunms.nextSetBit(j + 1), i++)
+                handler.set(i, res, descriptors[j].value(ectx, cctx, row));
+        }
 
         return res;
     }
@@ -399,15 +415,17 @@ public class TableDescriptorImpl extends 
NullInitializerExpressionFactory
         return F.t(Objects.requireNonNull(key), null);
     }
 
-    /** */
-    private RelDataType rowType(IgniteTypeFactory factory, boolean 
skipVirtual) {
+    /** {@inheritDoc} */
+    @Override public RelDataType rowType(IgniteTypeFactory factory, 
ImmutableBitSet usedColumns) {
         RelDataTypeFactory.Builder b = new RelDataTypeFactory.Builder(factory);
 
-        for (int i = 0; i < descriptors.length; i++) {
-            if (skipVirtual && virtualFlags.get(i))
-                continue;
-
-            b.add(descriptors[i].name(), descriptors[i].logicalType(factory));
+        if (usedColumns == null) {
+            for (int i = 0; i < descriptors.length; i++)
+                b.add(descriptors[i].name(), 
descriptors[i].logicalType(factory));
+        }
+        else {
+            for (int i = usedColumns.nextSetBit(0); i != -1; i = 
usedColumns.nextSetBit(i + 1))
+                b.add(descriptors[i].name(), 
descriptors[i].logicalType(factory));
         }
 
         return b.build();
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/TraitUtils.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/TraitUtils.java
index 602db23..4c7afbc 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/TraitUtils.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/trait/TraitUtils.java
@@ -20,7 +20,6 @@ package 
org.apache.ignite.internal.processors.query.calcite.trait;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import org.apache.calcite.plan.RelOptCluster;
@@ -32,6 +31,7 @@ import org.apache.calcite.plan.RelTraitDef;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.rel.RelCollationTraitDef;
+import org.apache.calcite.rel.RelCollations;
 import org.apache.calcite.rel.RelDistribution;
 import org.apache.calcite.rel.RelInput;
 import org.apache.calcite.rel.RelNode;
@@ -41,6 +41,7 @@ import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.Util;
+import org.apache.calcite.util.mapping.Mappings;
 import 
org.apache.ignite.internal.processors.query.calcite.rel.IgniteConvention;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteExchange;
 import org.apache.ignite.internal.processors.query.calcite.rel.IgniteRel;
@@ -49,8 +50,10 @@ import 
org.apache.ignite.internal.processors.query.calcite.rel.IgniteTrimExchang
 import org.apache.ignite.internal.util.typedef.F;
 import org.jetbrains.annotations.Nullable;
 
+import static org.apache.calcite.plan.RelOptUtil.permutationPushDownProject;
 import static 
org.apache.calcite.rel.RelDistribution.Type.BROADCAST_DISTRIBUTED;
 import static org.apache.calcite.rel.RelDistribution.Type.HASH_DISTRIBUTED;
+import static org.apache.calcite.rel.core.Project.getPartialMapping;
 import static 
org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions.any;
 import static 
org.apache.ignite.internal.processors.query.calcite.trait.IgniteDistributions.single;
 
@@ -332,4 +335,24 @@ public class TraitUtils {
 
         return out;
     }
+
+    /** */
+    public static RelCollation projectCollation(RelCollation collation, 
List<RexNode> projects, RelDataType inputRowType) {
+        if (collation.getFieldCollations().isEmpty())
+            return RelCollations.EMPTY;
+
+        Mappings.TargetMapping mapping = permutationPushDownProject(projects, 
inputRowType, 0, 0);
+
+        return collation.apply(mapping);
+    }
+
+    /** */
+    public static IgniteDistribution projectDistribution(IgniteDistribution 
distribution, List<RexNode> projects, RelDataType inputRowType) {
+        if (distribution.getType() != HASH_DISTRIBUTED)
+            return distribution;
+
+        Mappings.TargetMapping mapping = 
getPartialMapping(inputRowType.getFieldCount(), projects);
+
+        return distribution.apply(mapping);
+    }
 }
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/RexUtils.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/RexUtils.java
index 0aae659..2e1dc1a 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/RexUtils.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/RexUtils.java
@@ -18,18 +18,24 @@
 package org.apache.ignite.internal.processors.query.calcite.util;
 
 import java.util.Arrays;
-
+import java.util.List;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptPredicateList;
+import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeField;
 import org.apache.calcite.rex.RexBuilder;
 import org.apache.calcite.rex.RexExecutor;
+import org.apache.calcite.rex.RexInputRef;
+import org.apache.calcite.rex.RexLocalRef;
 import org.apache.calcite.rex.RexNode;
 import org.apache.calcite.rex.RexSimplify;
+import org.apache.calcite.rex.RexSlot;
 import org.apache.calcite.rex.RexUtil;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.util.Litmus;
 import org.apache.calcite.util.Util;
 import org.apache.ignite.internal.util.typedef.internal.U;
 
@@ -81,4 +87,33 @@ public class RexUtils {
 
         return builder.makeCall(SqlStdOperatorTable.CASE, operands);
     }
+
+    /** Returns whether a list of expressions projects the incoming fields. */
+    public static boolean isIdentity(List<? extends RexNode> projects, 
RelDataType inputRowType) {
+        return isIdentity(projects, inputRowType, false);
+    }
+
+    /** Returns whether a list of expressions projects the incoming fields. */
+    public static boolean isIdentity(List<? extends RexNode> projects, 
RelDataType inputRowType, boolean local) {
+        if (inputRowType.getFieldCount() != projects.size())
+            return false;
+
+        final List<RelDataTypeField> fields = inputRowType.getFieldList();
+        Class<? extends RexSlot> clazz = local ? RexLocalRef.class : 
RexInputRef.class;
+
+        for (int i = 0; i < fields.size(); i++) {
+            if (!clazz.isInstance(projects.get(i)))
+                return false;
+
+            RexSlot ref = (RexSlot) projects.get(i);
+
+            if (ref.getIndex() != i)
+                return false;
+
+            if (!RelOptUtil.eq("t1", projects.get(i).getType(), "t2", 
fields.get(i).getType(), Litmus.IGNORE))
+                return false;
+        }
+
+        return true;
+    }
 }
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessorTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessorTest.java
index 12448a9..04b854c 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessorTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/CalciteQueryProcessorTest.java
@@ -408,7 +408,8 @@ public class CalciteQueryProcessorTest extends 
GridCommonAbstractTest {
 
         assertNull(row);
     }
-    
+
+    /** for test purpose only. */
     public void testThroughput() {
         IgniteCache<Integer, Developer> developer = 
ignite.getOrCreateCache(new CacheConfiguration<Integer, Developer>()
             .setCacheMode(CacheMode.REPLICATED)
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/PlannerTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/PlannerTest.java
index c864e44..fa2f9c8 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/PlannerTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/PlannerTest.java
@@ -25,7 +25,9 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 import org.apache.calcite.config.CalciteConnectionConfig;
@@ -775,11 +777,17 @@ public class PlannerTest extends GridCommonAbstractTest {
                 .build()) {
             @Override public IgniteIndex getIndex(String idxName) {
                 return new IgniteIndex(null, null, null, null) {
-                    @Override public <Row> Iterable<Row> 
scan(ExecutionContext<Row> execCtx, Predicate<Row> filters,
-                        Supplier<Row> lowerIdxConditions, Supplier<Row> 
upperIdxConditions) {
+                    @Override public <Row> Iterable<Row> scan(
+                        ExecutionContext<Row> execCtx,
+                        Predicate<Row> filters,
+                        Supplier<Row> lowerIdxConditions,
+                        Supplier<Row> upperIdxConditions,
+                        Function<Row, Row> rowTransformer,
+                        @Nullable ImmutableBitSet requiredColunms
+                    ) {
                         return Linq4j.asEnumerable(Arrays.asList(
-                            row(execCtx, 0, "Igor", 0),
-                            row(execCtx, 1, "Roman", 0)
+                            row(execCtx, requiredColunms, 0, "Igor", 0),
+                            row(execCtx, requiredColunms, 1, "Roman", 0)
                         ));
                     }
                 };
@@ -808,11 +816,17 @@ public class PlannerTest extends GridCommonAbstractTest {
                 .build()) {
             @Override public IgniteIndex getIndex(String idxName) {
                 return new IgniteIndex(null, null, null, null) {
-                    @Override public <Row> Iterable<Row> 
scan(ExecutionContext<Row> execCtx, Predicate<Row> filters,
-                        Supplier<Row> lowerIdxConditions, Supplier<Row> 
upperIdxConditions) {
+                    @Override public <Row> Iterable<Row> scan(
+                        ExecutionContext<Row> execCtx,
+                        Predicate<Row> filters,
+                        Supplier<Row> lowerIdxConditions,
+                        Supplier<Row> upperIdxConditions,
+                        Function<Row, Row> rowTransformer,
+                        @Nullable ImmutableBitSet requiredColunms
+                    ) {
                         return Linq4j.asEnumerable(Arrays.asList(
-                            row(execCtx, 0, "Calcite", 1),
-                            row(execCtx, 1, "Ignite", 1)
+                            row(execCtx, requiredColunms, 0, "Calcite", 1),
+                            row(execCtx, requiredColunms, 1, "Ignite", 1)
                         ));
                     }
                 };
@@ -1143,10 +1157,15 @@ public class PlannerTest extends GridCommonAbstractTest 
{
                 .add("NAME", f.createJavaType(String.class))
                 .add("PROJECTID", f.createJavaType(Integer.class))
                 .build()) {
-            @Override public <Row> Iterable<Row> scan(ExecutionContext<Row> 
execCtx, Predicate<Row> filter) {
+            @Override public <Row> Iterable<Row> scan(
+                ExecutionContext<Row> execCtx,
+                Predicate<Row> filter,
+                Function<Row, Row> transformer,
+                ImmutableBitSet requiredColunms
+            ) {
                 return Arrays.asList(
-                    row(execCtx, 0, "Igor", 0),
-                    row(execCtx, 1, "Roman", 0)
+                    row(execCtx, requiredColunms, 0, "Igor", 0),
+                    row(execCtx, requiredColunms, 1, "Roman", 0)
                 );
             }
 
@@ -1165,10 +1184,15 @@ public class PlannerTest extends GridCommonAbstractTest 
{
                 .add("NAME", f.createJavaType(String.class))
                 .add("VER", f.createJavaType(Integer.class))
                 .build()) {
-            @Override public <Row> Iterable<Row> scan(ExecutionContext<Row> 
execCtx, Predicate<Row> filter) {
+            @Override public <Row> Iterable<Row> scan(
+                ExecutionContext<Row> execCtx,
+                Predicate<Row> filter,
+                Function<Row, Row> transformer,
+                ImmutableBitSet requiredColunms
+            ) {
                 return Arrays.asList(
-                    row(execCtx, 0, "Calcite", 1),
-                    row(execCtx, 1, "Ignite", 1)
+                    row(execCtx, requiredColunms, 0, "Calcite", 1),
+                    row(execCtx, requiredColunms, 1, "Ignite", 1)
                 );
             }
 
@@ -1406,17 +1430,35 @@ public class PlannerTest extends GridCommonAbstractTest 
{
 
         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,
+                Predicate<Row> filter,
+                Function<Row, Row> rowTransformer,
+                ImmutableBitSet requiredColunms
+            ) {
+                List<Row> checkRes0 = new ArrayList<>();
 
-            @Override public <Row> Iterable<Row> scan(ExecutionContext<Row> 
execCtx, Predicate<Row> filter) {
-                return Arrays.asList(
-                    row(execCtx, 0, 1),
-                    row(execCtx, 1, 2)
-                );
+                for (int i = 0; i < 10; ++i) {
+                    int col = ThreadLocalRandom.current().nextInt(1_000);
+
+                    Row r = row(execCtx, requiredColunms, col, col);
+
+                    if (rowTransformer != null)
+                        r = rowTransformer.apply(r);
+
+                    checkRes0.add(r);
+                }
+
+                checkRes.set(checkRes0);
+
+                return checkRes0;
             }
 
             @Override public NodesMapping mapping(PlanningContext ctx) {
@@ -1633,8 +1675,10 @@ public class PlannerTest extends GridCommonAbstractTest {
 
             assertFalse(res.isEmpty());
 
-            Assert.assertArrayEquals(new Object[]{1}, res.get(0));
-            Assert.assertArrayEquals(new Object[]{3}, res.get(1));
+            int pos = 0;
+
+            for (Object obj : checkRes.get())
+                Assert.assertArrayEquals((Object[]) obj, res.get(pos++));
         }
     }
 
@@ -2586,10 +2630,8 @@ public class PlannerTest extends GridCommonAbstractTest {
             RelNode phys = planner.transform(PlannerPhase.OPTIMIZATION, 
desired, rel);
 
             assertNotNull(phys);
-            assertEquals("" +
-                    "IgniteCorrelatedNestedLoopJoin(condition=[=(CAST(+($0, 
$1)):INTEGER, 2)], joinType=[inner])\n" +
-                    "  IgniteProject(DEPTNO=[$0])\n" +
-                    "    IgniteTableScan(table=[[PUBLIC, DEPT]])\n" +
+            
assertEquals("IgniteCorrelatedNestedLoopJoin(condition=[=(CAST(+($0, 
$1)):INTEGER, 2)], joinType=[inner])\n" +
+                    "  IgniteTableScan(table=[[PUBLIC, DEPT]], 
requiredColunms=[{0}])\n" +
                     "  IgniteProject(DEPTNO=[$2])\n" +
                     "    IgniteTableScan(table=[[PUBLIC, EMP]], 
filters=[=(CAST(+($cor1.DEPTNO, $t2)):INTEGER, 2)])\n",
                 RelOptUtil.toString(phys));
@@ -2606,19 +2648,27 @@ public class PlannerTest extends GridCommonAbstractTest 
{
     private static <T> List<T> select(List<T> src, int... idxs) {
         ArrayList<T> res = new ArrayList<>(idxs.length);
 
-        for (int idx : idxs) {
+        for (int idx : idxs)
             res.add(src.get(idx));
-        }
 
         return res;
     }
 
     /** */
-    private <Row> Row row(ExecutionContext<Row> ctx, Object... fields) {
+    private <Row> Row row(ExecutionContext<Row> ctx, ImmutableBitSet 
requiredColunms, Object... fields) {
         Type[] types = new Type[fields.length];
         for (int i = 0; i < fields.length; i++)
             types[i] = fields[i] == null ? Object.class : fields[i].getClass();
 
+        if (requiredColunms == null) {
+            for (int i = 0; i < fields.length; i++)
+                types[i] = fields[i] == null ? Object.class : 
fields[i].getClass();
+        }
+        else {
+            for (int i = 0, j = requiredColunms.nextSetBit(0); j != -1; j = 
requiredColunms.nextSetBit(j + 1), i++)
+                types[i] = fields[i] == null ? Object.class : 
fields[i].getClass();
+        }
+
         return ctx.rowHandler().factory(types).create(fields);
     }
 
@@ -2643,7 +2693,7 @@ public class PlannerTest extends GridCommonAbstractTest {
                 .replaceIf(RewindabilityTraitDef.INSTANCE, () -> 
RewindabilityTrait.REWINDABLE)
                 .replaceIf(DistributionTraitDef.INSTANCE, this::distribution);
 
-            return new IgniteTableScan(cluster, traitSet, relOptTbl, null);
+            return new IgniteTableScan(cluster, traitSet, relOptTbl);
         }
 
         /** {@inheritDoc} */
@@ -2651,12 +2701,21 @@ public class PlannerTest extends GridCommonAbstractTest 
{
             RelTraitSet traitSet = 
cluster.traitSetOf(IgniteConvention.INSTANCE)
                 .replaceIf(DistributionTraitDef.INSTANCE, this::distribution);
 
-            return new IgniteIndexScan(cluster, traitSet, relOptTbl, idxName, 
null);
+            return new IgniteIndexScan(cluster, traitSet, relOptTbl, idxName);
         }
 
         /** {@inheritDoc} */
-        @Override public RelDataType getRowType(RelDataTypeFactory 
typeFactory) {
-            return protoType.apply(typeFactory);
+        @Override public RelDataType getRowType(RelDataTypeFactory 
typeFactory, ImmutableBitSet bitSet) {
+            RelDataType rowType = protoType.apply(typeFactory);
+
+            if (bitSet != null) {
+                RelDataTypeFactory.Builder b = new 
RelDataTypeFactory.Builder(typeFactory);
+                for (int i = bitSet.nextSetBit(0); i != -1; i = 
bitSet.nextSetBit(i + 1))
+                    b.add(rowType.getFieldList().get(i));
+                rowType = b.build();
+            }
+
+            return rowType;
         }
 
         /** {@inheritDoc} */
@@ -2695,7 +2754,12 @@ public class PlannerTest extends GridCommonAbstractTest {
         }
 
         /** {@inheritDoc} */
-        @Override public <Row> Iterable<Row> scan(ExecutionContext<Row> root, 
Predicate<Row> filter) {
+        @Override public <Row> Iterable<Row> scan(
+            ExecutionContext<Row> execCtx,
+            Predicate<Row> filter,
+            Function<Row, Row> transformer,
+            ImmutableBitSet bitSet
+        ) {
             throw new AssertionError();
         }
 
@@ -2732,7 +2796,7 @@ public class PlannerTest extends GridCommonAbstractTest {
 
         /** {@inheritDoc} */
         @Override public Map<String, IgniteIndex> indexes() {
-            return indexes;
+            return Collections.unmodifiableMap(indexes);
         }
 
         /** {@inheritDoc} */
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/QueryChecker.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/QueryChecker.java
index 5510ab9..51f4af1 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/QueryChecker.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/QueryChecker.java
@@ -24,6 +24,7 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.Iterator;
 import java.util.List;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import org.apache.ignite.cache.query.FieldsQueryCursor;
 import org.apache.ignite.internal.processors.query.QueryEngine;
@@ -31,6 +32,7 @@ import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.X;
 import org.hamcrest.CoreMatchers;
 import org.hamcrest.Matcher;
+import org.hamcrest.core.SubstringMatcher;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
@@ -52,6 +54,59 @@ public abstract class QueryChecker {
     }
 
     /**
+     * Ignite table scan with projects matcher.
+     *
+     * @param schema  Schema name.
+     * @param tblName Table name.
+     * @return Matcher.
+     */
+    public static Matcher<String> containsAnyProject(String schema, String 
tblName) {
+        return containsSubPlan("IgniteTableScan(table=[[" + schema + ", " + 
tblName + "]], " + "requiredColunms=");
+    }
+
+    /**
+     * Ignite table scan with projects unmatcher.
+     *
+     * @param schema  Schema name.
+     * @param tblName Table name.
+     * @return Matcher.
+     */
+    public static Matcher<String> notContainsProject(String schema, String 
tblName) {
+        return CoreMatchers.not(containsSubPlan("IgniteTableScan(table=[[" + 
schema + ", " +
+            tblName + "]], " + "requiredColunms="));
+    }
+
+    /**
+     * Ignite table scan with projects unmatcher.
+     *
+     * @param schema  Schema name.
+     * @param tblName Table name.
+     * @return Matcher.
+     */
+    public static Matcher<String> containsProject(String schema, String 
tblName, int... requiredColunms) {
+        return matches(".*IgniteTableScan\\(table=\\[\\[" + schema + ", " +
+            tblName + "\\]\\], " + "requiredColunms=\\[\\{" +
+            Arrays.toString(requiredColunms)
+                .replaceAll("\\[", "")
+                .replaceAll("]", "") + "\\}\\]\\).*");
+    }
+
+    /**
+     * Ignite table scan with projects unmatcher.
+     *
+     * @param schema  Schema name.
+     * @param tblName Table name.
+     * @return Matcher.
+     */
+    public static Matcher<String> containsOneProject(String schema, String 
tblName, int... requiredColunms) {
+        return matchesOnce(".*IgniteTableScan\\(table=\\[\\[" + schema + ", " +
+            tblName + "\\]\\], " + "requiredColunms=\\[\\{" +
+            Arrays.toString(requiredColunms)
+                .replaceAll("\\[", "")
+                .replaceAll("]", "") + "\\}\\]\\).*");
+    }
+
+    /**
      * Ignite index scan matcher.
      *
      * @param schema  Schema name.
@@ -73,6 +128,51 @@ public abstract class QueryChecker {
         return CoreMatchers.containsString(subPlan);
     }
 
+    /** */
+    public static Matcher<String> matches(final String substring) {
+        return new SubstringMatcher(substring) {
+            /** {@inheritDoc} */
+            @Override protected boolean evalSubstringOf(String sIn) {
+                sIn = sIn.replaceAll("\n", "");
+
+                return sIn.matches(substring);
+            }
+
+            /** {@inheritDoc} */
+            @Override protected String relationship() {
+                return null;
+            }
+        };
+    }
+
+    /** */
+    public static Matcher<String> matchesOnce(final String substring) {
+        return new SubstringMatcher(substring) {
+            /** {@inheritDoc} */
+            @Override protected boolean evalSubstringOf(String sIn) {
+                sIn = sIn.replaceAll("\n", "");
+
+                return containsOnce(sIn, substring);
+            }
+
+            /** {@inheritDoc} */
+            @Override protected String relationship() {
+                return null;
+            }
+        };
+    }
+
+    /** Check only single matching. */
+    public static boolean containsOnce(final String s, final CharSequence 
substring) {
+        Pattern pattern = Pattern.compile(substring.toString());
+        java.util.regex.Matcher matcher = pattern.matcher(s);
+
+        if (matcher.find())
+            return !matcher.find();
+
+        return false;
+    }
+
     /**
      * Ignite any index can matcher.
      *
@@ -93,7 +193,7 @@ public abstract class QueryChecker {
     private final ArrayList<Matcher<String>> planMatchers = new ArrayList<>();
 
     /** */
-    private List<List<?>> expectedResult = null;
+    private List<List<?>> expectedResult;
 
     /** */
     private boolean ordered;
@@ -168,9 +268,8 @@ public abstract class QueryChecker {
                 assertThat(actualPlan, matcher);
         }
 
-        if (exactPlan != null) {
+        if (exactPlan != null)
             assertEquals(exactPlan, actualPlan);
-        }
 
         // Check result.
         List<FieldsQueryCursor<List<?>>> cursors =
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/QueryCheckerTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/QueryCheckerTest.java
new file mode 100644
index 0000000..045c235
--- /dev/null
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/QueryCheckerTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+import org.hamcrest.Matcher;
+import org.junit.Test;
+
+import static 
org.apache.ignite.internal.processors.query.calcite.QueryChecker.matchesOnce;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/** Query checker tests. */
+public class QueryCheckerTest {
+    /** */
+    @Test
+    public void testMatchesOnce() {
+        String plan = "PLAN=IgniteExchange(distribution=[single])\n  " +
+            "IgniteProject(NAME=[$2])\n    " +
+            "IgniteTableScan(table=[[PUBLIC, DEVELOPER]], projects=[[$t0]], 
requiredColunms=[{2}])\n  " +
+            "IgniteTableScan(table=[[PUBLIC, DEVELOPER]], projects=[[$t1]], 
requiredColunms=[{2, 3}])";
+
+        Matcher<String> matcherTbl = matchesOnce("IgniteTableScan");
+        Matcher<String> matcherPrj = matchesOnce("IgniteProject");
+
+        assertFalse(matcherTbl.matches(plan));
+        assertTrue(matcherPrj.matches(plan));
+    }
+}
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/rules/OrToUnionRuleTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/rules/OrToUnionRuleTest.java
index 6f8382b..48e041a 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/rules/OrToUnionRuleTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/rules/OrToUnionRuleTest.java
@@ -246,7 +246,7 @@ public class OrToUnionRuleTest extends 
GridCommonAbstractTest {
     /**
      *
      */
-    static class Product {
+    public static class Product {
         /** */
         long id;
 
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/rules/ProjectScanMergeRuleTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/rules/ProjectScanMergeRuleTest.java
new file mode 100644
index 0000000..146e256
--- /dev/null
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/rules/ProjectScanMergeRuleTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.rules;
+
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.cache.CacheMode;
+import org.apache.ignite.cache.QueryEntity;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.internal.processors.query.QueryEngine;
+import org.apache.ignite.internal.processors.query.calcite.QueryChecker;
+import org.apache.ignite.internal.processors.query.calcite.util.Commons;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Test;
+
+import static java.util.Collections.singletonList;
+import static 
org.apache.ignite.internal.processors.query.calcite.QueryChecker.containsOneProject;
+import static 
org.apache.ignite.internal.processors.query.calcite.QueryChecker.containsScan;
+import static 
org.apache.ignite.internal.processors.query.calcite.QueryChecker.notContainsProject;
+import static 
org.apache.ignite.internal.processors.query.calcite.rules.OrToUnionRuleTest.Product;
+
+/**
+ * Tests projection rule {@code 
org.apache.ignite.internal.processors.query.calcite.rule.ProjectScanMergeRule}
+ * This rule have a deal with only useful columns and.
+ * For example for tables: T1(f12, f12, f13) and T2(f21, f22, f23)
+ * sql execution: SELECT t1.f11, t2.f21 FROM T1 t1 INNER JOIN T2 t2 on t1.f11 
= t2.f22"
+ * need to eleminate all unused coluns and take into account only: f11, f21 
and f22 cols.
+ */
+public class ProjectScanMergeRuleTest extends GridCommonAbstractTest {
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        Ignite grid = startGridsMultiThreaded(2);
+
+        QueryEntity qryEnt = new QueryEntity();
+        qryEnt.setKeyFieldName("ID");
+        qryEnt.setKeyType(Integer.class.getName());
+        qryEnt.setValueType(Product.class.getName());
+
+        qryEnt.addQueryField("ID", Integer.class.getName(), null);
+        qryEnt.addQueryField("CATEGORY", String.class.getName(), null);
+        qryEnt.addQueryField("CAT_ID", Integer.class.getName(), null);
+        qryEnt.addQueryField("SUBCATEGORY", String.class.getName(), null);
+        qryEnt.addQueryField("SUBCAT_ID", Integer.class.getName(), null);
+        qryEnt.addQueryField("NAME", String.class.getName(), null);
+
+        qryEnt.setTableName("products");
+
+        final CacheConfiguration<Integer, Product> cfg = new 
CacheConfiguration<>(qryEnt.getTableName());
+
+        cfg.setCacheMode(CacheMode.PARTITIONED)
+            .setBackups(1)
+            .setQueryEntities(singletonList(qryEnt))
+            .setSqlSchema("PUBLIC");
+
+        IgniteCache<Integer, Product> devCache = grid.createCache(cfg);
+
+        devCache.put(1, new Product(1, "prod1", 1, "cat1", 11, "noname1"));
+        devCache.put(2, new Product(2, "prod2", 2, "cat1", 11, "noname2"));
+        devCache.put(3, new Product(3, "prod3", 3, "cat1", 12, "noname3"));
+
+        awaitPartitionMapExchange();
+    }
+
+    /** */
+    private QueryChecker checkQuery(String qry) {
+        return new QueryChecker(qry) {
+            @Override protected QueryEngine getEngine() {
+                return Commons.lookupComponent(grid(0).context(), 
QueryEngine.class);
+            }
+        };
+    }
+
+    /**
+     * Tests that the projects exist only for simple expressions without any 
predicates.
+     */
+    @Test
+    public void testProjects() {
+        checkQuery("SELECT NAME FROM products d;")
+            .matches(containsScan("PUBLIC", "PRODUCTS"))
+            .matches(containsOneProject("PUBLIC", "PRODUCTS", 7))
+            .returns("noname1")
+            .returns("noname2")
+            .returns("noname3")
+            .check();
+
+        checkQuery("SELECT SUBCAT_ID, NAME FROM products d;")
+            .matches(containsScan("PUBLIC", "PRODUCTS"))
+            .matches(containsOneProject("PUBLIC", "PRODUCTS", 6, 7))
+            .returns(11, "noname1")
+            .returns(11, "noname2")
+            .returns(12, "noname3")
+            .check();
+
+        checkQuery("SELECT NAME FROM products d WHERE CAT_ID > 1;")
+            .matches(containsScan("PUBLIC", "PRODUCTS"))
+            .matches(notContainsProject("PUBLIC", "PRODUCTS"))
+            .returns("noname2")
+            .returns("noname3")
+            .check();
+    }
+}
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/testsuites/IgniteCalciteTestSuite.java
 
b/modules/calcite/src/test/java/org/apache/ignite/testsuites/IgniteCalciteTestSuite.java
index 912d8dc..6742375 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/testsuites/IgniteCalciteTestSuite.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/testsuites/IgniteCalciteTestSuite.java
@@ -21,11 +21,13 @@ import 
org.apache.ignite.internal.processors.query.calcite.CalciteBasicSecondary
 import 
org.apache.ignite.internal.processors.query.calcite.CalciteQueryProcessorTest;
 import org.apache.ignite.internal.processors.query.calcite.CancelTest;
 import org.apache.ignite.internal.processors.query.calcite.PlannerTest;
+import org.apache.ignite.internal.processors.query.calcite.QueryCheckerTest;
 import 
org.apache.ignite.internal.processors.query.calcite.exec.ClosableIteratorsHolderTest;
 import 
org.apache.ignite.internal.processors.query.calcite.exec.rel.ContinuousExecutionTest;
 import 
org.apache.ignite.internal.processors.query.calcite.exec.rel.ExecutionTest;
 import org.apache.ignite.internal.processors.query.calcite.jdbc.JdbcQueryTest;
 import 
org.apache.ignite.internal.processors.query.calcite.rules.OrToUnionRuleTest;
+import 
org.apache.ignite.internal.processors.query.calcite.rules.ProjectScanMergeRuleTest;
 import org.junit.runner.RunWith;
 import org.junit.runners.Suite;
 
@@ -36,13 +38,16 @@ import org.junit.runners.Suite;
 @Suite.SuiteClasses({
     PlannerTest.class,
     OrToUnionRuleTest.class,
+    ProjectScanMergeRuleTest.class,
     ExecutionTest.class,
     ClosableIteratorsHolderTest.class,
     ContinuousExecutionTest.class,
     CalciteQueryProcessorTest.class,
     JdbcQueryTest.class,
     CalciteBasicSecondaryIndexIntegrationTest.class,
-    CancelTest.class
+    CancelTest.class,
+    QueryCheckerTest.class,
+    ProjectScanMergeRuleTest.class
 })
 public class IgniteCalciteTestSuite {
 }

Reply via email to